Redis Sentinel を VLAN と keepalived でセキュアかつ冗長化する (Akamai)

Redis Sentinel は Redis の高可用性ソリューションです。アプリケーションからも高可用性を担保するために HAProxy を冗長化するパターンがあります。Akamai のクラウドでは IP Sharing 機能が提供されており、Private IP を使って HAProxy を冗長化することができます。手順については以下の記事が参考になります。

https://qiita.com/110hideki/private/330da674831e1dd4cc27

Redis Sentinel の環境を完全にプライベートのネットワークに閉じて構築するために、VLAN 対応する記事も紹介しています。

https://qiita.com/110hideki/items/360212e5d8f07a53a695

HAProxy の冗長化、VLAN 対応はそれぞれ個別に実現することを確認していますが、本記事は２つの要素（冗長化とセキュリティ）を合わせた方法を説明します。主な確認事項は VLAN 上の HAProxy の冗長化として ``keepalived`` を使うことになります。最終的には下図の構成となります。


![Redis-VLAN-HAProxy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/5522affa-6769-8033-a98c-d6fb801f4eb1.jpeg)

:::note
Public IP と Private IP とは違って、VLAN は内部のネットワークに閉じているので、``lelastic`` のようなツールを使う必要はありません。
:::

:::note
VLAN 内では自由に IP を使えるので、IP Sharing は不要です。
:::

# Redis Sentinel の VLAN 環境を用意する

以下の記事を参照して VLAN の環境を用意します。

https://qiita.com/110hideki/items/360212e5d8f07a53a695

本記事ではまず下図のような構成を前提として用意しました。
![redis-vlan-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/29ecc4a4-8341-1aa8-c3c6-8fc3cc77c290.jpeg)

# /etc/hosts
VLAN の IP は以下のように設定します。
```
# VLAN
10.0.0.11 redis-1v # Redis Server 1
10.0.0.12 redis-2v # Redis Server 2
10.0.0.13 redis-3v # Redis Server 3
10.0.0.1 vlan-router # Act as a Router
10.0.0.21 vlan-haproxy1 # HAProxy 1
10.0.0.22 vlan-redisclient $ Application
10.0.0.23 vlan-haproxy2 # HAProxy 2
10.0.0.31 vlan-haproxy  # HAProxy (VIP)
```

## HAProxy を追加する

次に下記の記事を参考に haproxy2 を用意します。

https://qiita.com/110hideki/private/330da674831e1dd4cc27

下図の ``vlan-haproxy2`` を用意し、Redis クライアントから動作するように設定します。
![redis-vlan-haproxy2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/5a6ce337-8427-7a8d-5096-765d8c73ce08.jpeg)

Linode の Clone 機能を使い、作業を簡素化します。

### Clone
Cloud Manager 上で、``Clone`` を選択します。
![clone.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/0b051afc-381b-074f-34b8-f92571fda1e2.jpeg)

![clone-linode.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/5a457457-d92c-760b-c748-ef237b3213e6.jpeg)
Clone Linode タブの中で、Clone 元のインスタンスを選択します。
![clone-source-host.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/98ceb159-2ef4-0cdb-33e6-fcf13b026721.jpeg)
地域と Linode Plan を選択します。
![clone-region.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/4c235290-a4fa-dbdf-82ba-bdf9ceab12d2.jpeg)
名称を自由に変えます。
![redissentinel-nj_haproxy2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/4d904cfb-b8ca-8cf0-87f6-a86b3d3583a7.jpeg)
VLAN の設定を変更します。``10.0.0.23/24`` を割り当てます。
![haproxy2-vlan.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/4d1d5d52-c95c-a2a6-706a-22daadc38b38.jpeg)
作られた後のインターフェースです。
![haproxy2-vlan-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/02950bc9-eeaf-250c-681f-73f78ae67693.jpeg)
クローン後は自動でインスタンスは立ち上がりません。 ``Power On`` ボタンを押して起動します。
![haproxy2-poweron.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/770adc20-08ce-b5ca-673e-96be5fa204a7.jpeg)


起動後、[セキュリティ設定](https://www.linode.com/docs/products/compute/compute-instances/guides/set-up-and-secure/)を再確認し、その中で次のようにホスト名を変更します。

```
hostnamectl set-hostname hproxy2
```

## haproxy.cfg on haproxy2
Bind 設定は次のようにします。
```
frontend ft_redis
    bind *:6379 name redis
    default_backend bk_redis
```
haproxy1 でも同様な設定にします。設定変更した場合、 ``haproxy`` を再起動します。

```
systemctl restart haproxy
```

## firewalld on Redis1/2/3
Redisの各サーバーで 新しい haproxy2 と VIP からアクセスできるように ``firewalld`` の設定変更をします。``10.0.0.31`` は、このあとで設定する Virtual IP 用の設定です。
```
  <source address="10.0.0.23"/>
```

```
  <source address="10.0.0.31"/>
```
設定変更後、``firewalld`` を再起動します。
```
firewall-cmd --reload
```

## haproxy2 からの確認
全ての Redis Server に接続できることを確認します。
```
haproxy2:~# redis-cli -h redis-1v --tls --cacert ca.crt info replication | grep role
role:slave
haproxy2:~# redis-cli -h redis-2v --tls --cacert ca.crt info replication | grep role
role:master
haproxy2:~# redis-cli -h redis-3v --tls --cacert ca.crt info replication | grep role
role:slave
```

## haproxy1 からの確認
haproxy1 からつながることを確認します。
```
haproxy1:~# redis-cli -h vlan-haproxy2 --tls --cacert ca.crt info replication | grep role
role:master
haproxy1:~# redis-cli -h vlan-haproxy1 --tls --cacert ca.crt info replication | grep role
role:master
```
これまでの作業で下図の構成となります。まだ ``keepalived`` は動いてないので VIP がない状況です。
![haproxy2-setup-complete.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/e6475b02-5596-1c6e-19fd-1cb1235f1361.jpeg)

# keepalived の設定

``keepalived``を HAProxy 1/2 にインストールし、``10.0.0.31`` の Virtual IP (VIP) を動くように設定します。

![haproxy-keepalived.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/a4726761-0e2c-fbee-f6bd-7cca6209429f.jpeg)


## keepalived のインストール

haproxy1 と haproxy2 に ``keepalived`` をインストールします。
```
apt install keepalived
```

## keepalived.conf の設定
haproxy1 と happroxy2 で ``keepalived.conf`` を設定します。設定内容はそれぞれ異なります。
```
vi /etc/keepalived/keepalived.conf
```

### haproxy1 の設定
```
vrrp_instance Instance1 {
    state MASTER
    interface eth0
    virtual_router_id 10
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass passWord
    }
    unicast_src_ip 10.0.0.21
    unicast_peer {
       10.0.0.23
    }
    virtual_ipaddress {
        10.0.0.31
    }
}
```

### haproxy2 の設定

haproxy1 との違いです。
1.``state`` が ``MASTER`` ではなく ``BACKUP`` とする
2. priority を ``100`` ではなく ``99`` とする
3. ``Unicast_src_ip`` を自分自身の IP とする
4. ``unicast_peer`` を相手の IP とする
```
vrrp_instance Instance1 {
    state BACKUP
    interface eth0
    virtual_router_id 10
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass passWord
    }
    unicast_src_ip 10.0.0.23
    unicast_peer {
       10.0.0.21
    }
    virtual_ipaddress {
        10.0.0.31
    }
}
```

## keepalived の起動

haproxy1/2 で ``keepalived`` を動かします。
```
sudo systemctl enable keepalived
sudo systemctl start keepalived
```

## haproxy1 の IP の確認
``eth0`` デバイスに ``10.0.0.31`` が割り当てられています。
```
haproxy1:/etc/keepalived# ip -4 a show dev eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.0.21/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.0.0.31/32 scope global eth0
       valid_lft forever preferred_lft forever
```

## HAProsxy2 の IP の確認
``eth0`` デバイスに ``10.0.0.31`` は割り当てられていません。
```
haproxy2:/etc/keepalived# ip -4 a show dev eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.0.23/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
```



# keepalived の動作確認

Redisc Client (10.0.0.22) で３つの端末を立ち上げて、次の３つのコマンドを起動します。 
```
ping 10.0.0.31
```

```
ping 10.0.0.21
```

```
redis-cli -h vlan-haproxy --tls --cacert redis-nj_ca.crt
```

haproxy1 で reboot します。

```
reboot
```
この後下図のように動作することを見ていきます。
![redis-keepalived-haproxy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/cb550942-b90e-b494-5264-f0afd6378ecb.jpeg)

## haproxy1 への ping の結果
haproxy1 を reboot したことで、Redis Client (10.0.0.22) からの ping は途中で応答がなくなります。
```
64 bytes from 10.0.0.21: icmp_seq=42 ttl=64 time=0.767 ms
64 bytes from 10.0.0.21: icmp_seq=43 ttl=64 time=2.02 ms
64 bytes from 10.0.0.21: icmp_seq=44 ttl=64 time=3.16 ms
64 bytes from 10.0.0.21: icmp_seq=45 ttl=64 time=1.37 ms
64 bytes from 10.0.0.21: icmp_seq=46 ttl=64 time=0.628 ms



```
## VIP への ping の結果
haproxy1 が持っていた ``10.0.0.31`` が haproxy2 に引き継がれるかを確認します。先程の ``10.0.0.21`` に到達できないタイミングで ``10.0.0.31`` は到達できています。
```
64 bytes from 10.0.0.31: icmp_seq=42 ttl=64 time=0.467 ms
64 bytes from 10.0.0.31: icmp_seq=43 ttl=64 time=0.446 ms
64 bytes from 10.0.0.31: icmp_seq=44 ttl=64 time=0.355 ms
64 bytes from 10.0.0.31: icmp_seq=45 ttl=64 time=0.447 ms
64 bytes from 10.0.0.31: icmp_seq=46 ttl=64 time=0.365 ms
64 bytes from 10.0.0.31: icmp_seq=47 ttl=64 time=0.435 ms
64 bytes from 10.0.0.31: icmp_seq=48 ttl=64 time=0.372 ms
64 bytes from 10.0.0.31: icmp_seq=49 ttl=64 time=0.369 ms
64 bytes from 10.0.0.31: icmp_seq=50 ttl=64 time=0.357 ms
```

このとき、haproxy2 の IP は次のように変更されています。keepalived が動作し、haproxy2 が ``10.0.0.31`` を持っています。
```
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.0.23/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.0.0.31/32 scope global eth0
       valid_lft forever preferred_lft forever
```

## Redis Client から HAProxy 経由で Redis Server に接続する
haproxy1 を shutdown しても、継続して Redis Server に接続できています。

```
redisclient:~# redis-cli -h vlan-haproxy --tls --cacert redis-nj_ca.crt
vlan-haproxy:6379> role
1) "master"
2) (integer) 407029129
3) 1) 1) "10.0.0.13"
      2) "6379"
      3) "407029129"
   2) 1) "10.0.0.11"
      2) "6379"
      3) "407029129"
vlan-haproxy:6379> <- このタイミングで haproxy1 を shutdown
vlan-haproxy:6379> get scott　
"tiger"
vlan-haproxy:6379> role
1) "master"
2) (integer) 407050682
3) 1) 1) "10.0.0.13"
      2) "6379"
      3) "407050416"
   2) 1) "10.0.0.11"
      2) "6379"
      3) "407050416"
```

## Reboot 後
haproxy1 が立ち上がったことで、haproxy2 の IP から ``10.0.0.31`` が見えなくなります。
```
@haproxy2:/etc/keepalived# ip -4 a show dev eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.0.23/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
```
haproxy2 の ``keepalived.conf`` の中の次の行の設定によって、haproxy1 の設定値よりも優先度が低いと判断され、IP は haproxy1 に優先権が与えられます。
```
    priority 99
```

haproxy1 にログインして、IP を確認します。
```
@haproxy1:~# ip -4 a show dev eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 10.0.0.21/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.0.0.31/32 scope global eth0
       valid_lft forever preferred_lft forever
```

## /var/log/syslog
``keepalived`` のログは ``/var/log/syslog`` に記録されます。haproxy1/2のログを見ると、それぞれ、 Master と BACKUP の遷移が見られます。

### haproxy2
```
Jul 19 16:21:00 haproxy2 Keepalived_vrrp[44007]: (Instance1) Entering MASTER STATE
Jul 19 16:23:55 haproxy2 Keepalived_vrrp[44007]: (Instance1) Master received advert from 10.0.0.21 with higher priority 100, ours 99
Jul 19 16:23:55 haproxy2 Keepalived_vrrp[44007]: (Instance1) Entering BACKUP STATE
Jul 19 16:39:16 haproxy2 Keepalived_vrrp[44007]: (Instance1) Entering MASTER STATE
Jul 19 16:41:59 haproxy2 Keepalived_vrrp[44007]: (Instance1) Master received advert from 10.0.0.21 with higher priority 100, ours 99
Jul 19 16:41:59 haproxy2 Keepalived_vrrp[44007]: (Instance1) Entering BACKUP STATE
```

### haproxy1 
```
Jul 19 16:20:59 haproxy1 Keepalived[528]: Stopping
Jul 19 16:20:59 haproxy1 systemd[1]: Stopping Keepalive Daemon (LVS and VRRP)...
Jul 19 16:23:50 haproxy1 systemd[1]: Starting Keepalive Daemon (LVS and VRRP)...
Jul 19 16:23:51 haproxy1 Keepalived[541]: Starting Keepalived v2.2.4 (08/21,2021)
Jul 19 16:23:51 haproxy1 Keepalived[541]: Running on Linux 6.2.9-x86_64-linode160 #1 SMP PREEMPT_DYNAMIC Wed Apr  5 15:30:32 EDT 2023 (built for Linux 5.15.27)
Jul 19 16:23:51 haproxy1 Keepalived[541]: Command line: '/usr/sbin/keepalived' '--dont-fork'
Jul 19 16:23:51 haproxy1 Keepalived[541]: Configuration file /etc/keepalived/keepalived.conf
Jul 19 16:23:51 haproxy1 Keepalived[541]: NOTICE: setting config option max_auto_priority should result in better keepalived performance
Jul 19 16:23:51 haproxy1 Keepalived[541]: Starting VRRP child process, pid=550
Jul 19 16:23:51 haproxy1 Keepalived[541]: Startup complete
Jul 19 16:23:51 haproxy1 systemd[1]: Started Keepalive Daemon (LVS and VRRP).
Jul 19 16:23:51 haproxy1 Keepalived_vrrp[550]: (Instance1) Entering BACKUP STATE (init)
Jul 19 16:23:52 haproxy1 Keepalived_vrrp[550]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:23:53 haproxy1 Keepalived_vrrp[550]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:23:54 haproxy1 Keepalived_vrrp[550]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:23:55 haproxy1 Keepalived_vrrp[550]: (Instance1) Entering MASTER STATE
Jul 19 16:39:16 haproxy1 Keepalived[541]: Stopping
Jul 19 16:39:16 haproxy1 systemd[1]: Stopping Keepalive Daemon (LVS and VRRP)...
Jul 19 16:41:55 haproxy1 systemd[1]: Starting Keepalive Daemon (LVS and VRRP)...
Jul 19 16:41:55 haproxy1 Keepalived[537]: Starting Keepalived v2.2.4 (08/21,2021)
Jul 19 16:41:55 haproxy1 Keepalived[537]: Running on Linux 6.2.9-x86_64-linode160 #1 SMP PREEMPT_DYNAMIC Wed Apr  5 15:30:32 EDT 2023 (built for Linux 5.15.27)
Jul 19 16:41:55 haproxy1 Keepalived[537]: Command line: '/usr/sbin/keepalived' '--dont-fork'
Jul 19 16:41:55 haproxy1 Keepalived[537]: Configuration file /etc/keepalived/keepalived.conf
Jul 19 16:41:55 haproxy1 Keepalived[537]: NOTICE: setting config option max_auto_priority should result in better keepalived performance
Jul 19 16:41:55 haproxy1 Keepalived[537]: Starting VRRP child process, pid=547
Jul 19 16:41:56 haproxy1 Keepalived[537]: Startup complete
Jul 19 16:41:56 haproxy1 systemd[1]: Started Keepalive Daemon (LVS and VRRP).
Jul 19 16:41:56 haproxy1 Keepalived_vrrp[547]: (Instance1) Entering BACKUP STATE (init)
Jul 19 16:41:56 haproxy1 Keepalived_vrrp[547]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:41:57 haproxy1 Keepalived_vrrp[547]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:41:58 haproxy1 Keepalived_vrrp[547]: (Instance1) received lower priority (99) advert from 10.0.0.23 - discarding
Jul 19 16:41:59 haproxy1 Keepalived_vrrp[547]: (Instance1) Entering MASTER STATE
```

最終的に元の状態に戻ります。

![redis-after-haproxy1-reboot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/e62967a8-ee93-c18c-ea55-d765bc56eec1.jpeg)


# まとめ
Redis Sentinel は Redis の高可用性ソリューションです。VLAN を使うことで、セキュアなネットワークの構築ができます。そして、HAProxy で全体の利便性を高めながら、Keepalived を使うことで HAProxy の高可用性の環境を構築することができます。