Redis Server にフォワードする HAProxy を IP Sharing で冗長化する (Akamai)

Redis Server をクラスタ化しないマスター/レプリカ構成の場合、クライアントが常にマスターサーバーに接続できるように HAProxy を手前に配置するパターンがあります。HAProxy が SPOF (Single Point of Failure) になるので、本記事では HAProxy を冗長化するパターンを説明します。

![redis-haproxy-ipsharing.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/479acb4f-38c5-26f8-42cb-c54b9c73718c.jpeg)

:::note warn
本記事では Private IP を使う例を説明します。VLAN を使うケースは別の記事で説明予定です。
:::

HAProxy の IP Sharing 化については下記の資料を参照します。

https://qiita.com/kabe-akam/items/a80f4a71358a27605076


# Redis Sentinel の準備

以下の記事を参考にして、Redis Sentinel を使った 3 ノード構成と HAProxy と Redis Client の環境を用意します。

https://qiita.com/110hideki/private/83766a577e6f74d72121

# 追加の HAProxy の準備

先に用意した HAProxy の構成を追加で一台作成します。それぞれ 2.4.22 で動作しています。

```
ord-haproxy1:~# haproxy -v
HAProxy version 2.4.22-0ubuntu0.22.04.1 2023/03/22 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.22.html
Running on: Linux 6.2.9-x86_64-linode160 #1 SMP PREEMPT_DYNAMIC Wed Apr  5 15:30:32 EDT 2023 x86_64
```

```
ord-haproxy2:~# haproxy -v
HAProxy version 2.4.22-0ubuntu0.22.04.1 2023/03/22 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.22.html
Running on: Linux 6.2.9-x86_64-linode160 #1 SMP PREEMPT_DYNAMIC Wed Apr  5 15:30:32 EDT 2023 x86_64
```

```
ord-redisclient:~# redis-cli -v
redis-cli 6.0.16
```

# Redis Client からの動作確認

２つの HAProxy を配置し、それぞれ、Redis Sentinel を構成したノードに接続しています。
![redis-haproxy-2-haproxy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/5d0a73e1-dc38-4c2f-0221-8255156530af.jpeg)

```
ord-redisclient:~# redis-cli -h ord-haproxy1 --tls --cacert redissentinel-ord_ca.crt info replication | egrep role
role:master
```

```
ord-redisclient:~# redis-cli -h ord-haproxy2 --tls --cacert redissentinel-ord_ca.crt info replication | egrep role
role:master
```

このときの ``/etc/hosts`` の情報です。
```
# Redis
192.168.XXX.1 ord-redissentinel1 redis-1
192.168.XXX.2 ord-redissentinel2 redis-2
192.168.XXX.3 ord-redissentinel3 redis-3

192.168.0.1 ord-haproxy1
192.168.0.2  ord-haproxy2
192.168.XXX.11 ord-redisclient
```
この状態では 各 Redis Server の Firewalld では Redis Client からのアクセスも許可しているので、それぞれにアクセスすると ``redis-2`` と ``redis-3`` がレプリカであることが分かります。

```
ord-redisclient:~# redis-cli -h redis-1 --tls --cacert redissentinel-ord_ca.crt info replication | egrep role
role:master

ord-redisclient:~# redis-cli -h redis-2 --tls --cacert redissentinel-ord_ca.crt info replication | egrep role
role:slave

ord-redisclient:~# redis-cli -h redis-3 --tls --cacert redissentinel-ord_ca.crt info replication | egrep role
role:slave
```

# IP Sharing の導入

![redis-haproxy-2-haproxy-ipsharing.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/0ebecba7-2769-bca3-9d00-1379890b1e90.jpeg)

IP Sharing は一つの IP を複数のノードで共有することができる技術です。一つのノードだけがその IP を使うことができ、内部的な障害検知によってIPを保持しているノードがダウンしたときに別のノードがそのIPを引き取ることができます。

下図のように２つの HAProxy があり、それぞれ ``192.168.0.1`` と ``192.168.0.2`` を持っています。haproxy1 はさらに別の ``192.168.0.3`` を持っている状態です。
![redis-ipsharing-before.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/9a8a2d81-2de4-236e-16d8-8d669d715f8f.jpeg)

haproxy2 のネットワーク設定で ``192.168.0.3`` を IP Sharing として登録し、適切な設定をすると、haproxy1 の障害時に haproxy2 が ``192.168.0.3`` を使うことができます。
![redis-ipsharing-after.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/13a13b07-7217-3fcb-6647-9e55980c1f66.jpeg)

## haproxy1 に Private IP を追加
![linode-haproxy1-2-privateip-mark.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/3d0d6862-c91c-0280-e263-afc0c3c1cda6.jpeg)

:::note warn
追加するには Cloud Manager からサポートサービスに連絡する必要があります。
:::

## haproxy2 に Shared IP として追加
haproxy2 のネットワーク設定から IP Sharing を選択します。
![linode-haproxy2-ipsharing-button.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/5d0130c0-6a0e-cba0-fe71-86b660a7e4f8.jpeg)

IP Sharing の画面から、 haproxy1 に追加した ``192.168.0.3`` を選択します。
![linode-haproxy2-sharedip-popup-comment.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/b82646c8-be35-127a-b79b-f046e5ff94d0.jpeg)

追加されれば、''IP Sharing uploaded successfully`` のようなメッセージが表示されるので、ポップアップ画面を閉じます。

![linode-haproxy2-ipsharing-done.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/59b8faa1-61cd-4b15-40f2-587a34cf7c49.jpeg)

haproxy2 のネットワーク設定で追加した IP が ``Shared`` として登録されています。

![linode-haproxy2-network-sharedip.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/c19068b2-14b6-ea0e-881c-a0d3c46dcfc8.jpeg)

# ネットワークに Shared IP を加える (haproxy1 と haproxy2 の共通設定)
先程の作業はプラットフォーム上の設定です。次に Linux 上の設定を行います。
## /etc/netplan/01-netcfg.yaml

```
# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: yes
    lo:　# ここから追加
      match:
        name: lo
      addresses: 
        - 192.168.0.3/32 # /17 ではなく #32 ですので注意
```

設定を反映させます。
```
netplan apply
```
設定後、双方のサーバーの ``lo`` デバイスに 登録した Private IP アドレスが表示されます。
```
ord-haproxy1:/etc/netplan# ip -4 a show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet 192.168.0.3/32 scope global lo
       valid_lft forever preferred_lft forever
```

```
ord-haproxy2:/etc/netplan# ip -4 a show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet 192.168.0.3/32 scope global lo
       valid_lft forever preferred_lft forever
```
## lelastic のインストールと設定
``lelastic`` は Linode の独自ツールです。エラスティックな IP をできるだけ簡単に使用できるように設計されたクライアントです。

以下のコマンドを実行します。
```
version=v0.0.6
curl -LO https://github.com/linode/lelastic/releases/download/$version/lelastic.gz
gunzip lelastic.gz
chmod 755 lelastic
sudo mv lelastic /usr/local/bin/
```

# lelastic.service のファイルを作成する

```
ord-haproxy2:~# vi /etc/systemd/system/lelastic.service
```
ファイルは以下のようにします。``[ID]`` と ``[role]`` は後で編集します。
```
[Unit]
Description= Lelastic
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/lelastic -dcid [ID] -[role] &
ExecReload=/bin/kill -s HUP $MAINPID

[Install]
WantedBy=multi-user.target
```

``[ID]`` はデータセンターの番号を入れます。下記のページの表に記載があります。

https://www.linode.com/docs/products/compute/compute-instances/guides/failover/#ip-sharing-availability

``[role]`` には Shared IP をプライマリで動かすホスト側(haproxy1) では ``primary`` を、共有されるホスト側(haproxy2) では ``secondary`` と書き換えます。

## HAproxy 1 の設定

```
ExecStart=/usr/local/bin/lelastic -dcid 18 -primary  &
```

``-loglevel debug`` などを追記すると、``/var/log/syslog`` により詳細なログが記録されます。

## HAproxy 2 の設定
```
ExecStart=/usr/local/bin/lelastic -dcid 18 -secondary  &
```
# lelastic の起動 (haproxy1 と haproxy2 の共通作業)
```
chmod 644 /etc/systemd/system/lelastic.service
systemctl start lelastic
systemctl enable lelastic
```
# HAProxy の Bind 設定の変更

![redis-haproxy-binding.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/953edbd3-0255-0cb5-def3-a58c857c55a2.jpeg)


## haproxy1
```
frontend ft_redis
#   HAProxy Private IP address
#   bind 191.168.0.1:6379 name redis
#   shared IP address
    bind 192.168.0.3:6379 name redis
    default_backend bk_redis
```

:::note
次のような設定にすることもできます。
```
bind *:6379 name redis
```
:::

## haproxy2

```
frontend ft_redis
#   HAProxy Private IP address
#   bind 191.168.0.2:6379 name redis
#   shared IP address
    bind 192.168.0.3:6379 name redis
    default_backend bk_redis
```

# Redis Client から IP Sharing が動作するかを確認

現在の状態です。

![redis-sentinel-haproxy-ipsharing-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/e8561cc4-c86c-296a-7e4a-016c747c9385.jpeg)

## Redis クライアントに IP Sharing のアドレスを登録
IP Sharing のアドレスを ``/etc/hosts`` に登録します。
```
192.168.XXX.1 ord-redissentinel1 redis-1
192.168.XXX.2 ord-redissentinel2 redis-2
192.168.XXX.3 ord-redissentinel3 redis-3
# END redis servers
192.168.0.1 ord-haproxy1
192.168.0.2  ord-haproxy2
192.168.0.3 ord-haproxy # 追加した行です
192.168.XXX.11 ord-redisclient
```

## Redis-cli からの接続確認 (その 1)
```
ord-redisclient:~# redis-cli -h ord-haproxy --tls --cacert redissentinel-ord_ca.crt ping
PONG
```

```
ord-redisclient:~# redis-cli -h ord-haproxy --tls --cacert redissentinel-ord_ca.crt info replication | grep role
role: master
```
Read/Write もできる状態です。
```
ord-redisclient:~# redis-cli -h ord-haproxy --tls --cacert redissentinel-ord_ca.crt
ord-haproxy:6379> set scott tiger
OK
ord-haproxy:6379> get scott
"tiger"
ord-haproxy:6379> get scott
"tiger"
ord-haproxy:6379> set scott lion
OK
ord-haproxy:6379> get scott
"lion"
ord-haproxy:6379>
```

## Primary と動作しているホストで lelastic を停止
マスターサーバーのプロセスを停止します。
```
ord-haproxy1:/etc/haproxy# systemctl stop lelastic
```
:::note
サーバー停止中でも同様に動作します。
:::

:::note warn
この仕組みでは ``systemctl stop haproxy`` は検知できません。
:::

## Redis-cli からの接続確認 (その 2)
一瞬接続エラーが出ますが、すぐに新しいコマンドに対して応答が返ってきます。
```
ord-haproxy:6379> get scott
Could not connect to Redis at ord-haproxy:6379: SSL_connect failed: unexpected eof while reading
not connected> get scott
"lion"
```
``redis-2`` がマスターに昇格していますが、その状況をクライアント側は気にすることなく接続できています。
```
ord-haproxy1:/etc/haproxy# redis-cli -h redis-2 --tls --cacert /etc/haproxy/tls/redissentinel-ord_ca.crt info replication | grep role
role:master
```

![redis-sentinel-haproxy-ipsharing-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/d8f8241c-fd46-4d07-3868-0ab21ddf5ff8.jpeg)


## Secondary と動作しているホストで lelastic を停止
```
ord-haproxy2:/etc/systemd/system# systemctl stop lelastic
```
## Redis-cli からの接続確認 (その 3)
応答はなくなります。
```
ord-haproxy:6379> get scott

```

![redis-sentinel-haproxy-ipsharing-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/e7d370f7-b4f6-e8a6-a14d-eb553e50d048.jpeg)

## Primary と動作しているホストで lelastic を再開
```
ord-haproxy1:/etc/haproxy# systemctl start lelastic
```
## Redis-cli からの接続確認 (その 4)
すぐに接続されます。
```
ord-haproxy:6379> get scott
"lion"
(65.31s)
ord-haproxy:6379> get scott
"lion"
ord-haproxy:6379> set scott tiger
OK
ord-haproxy:6379> get scott
"tiger"
```

![redis-sentinel-haproxy-ipsharing-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/1bbe34d2-0418-bdbc-2a5e-8c3555dc291f.jpeg)


## Redis マスターサーバーの停止

```
redis-1:~# systemctl stop redis-server
```
``redis-2`` がマスターに昇格しています。
```
ord-haproxy1:/etc/haproxy# redis-cli -h redis-2 --tls --cacert /etc/haproxy/tls/redissentinel-ord_ca.crt info replication | grep role
role:master
```
## Redis-cli からの接続確認 (その 5)
``redis-2`` がマスターに昇格していますが、その状況をクライアント側は気にすることなく接続できています。
```
ord-haproxy:6379> get scott
Could not connect to Redis at ord-haproxy:6379: SSL_connect failed: unexpected eof while reading
not connected> get scott
"lion"
ord-haproxy:6379> get scott
"lion"
(65.31s)
ord-haproxy:6379> get scott
"lion"
```
![redis-sentinel-haproxy-ipsharing-5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/0da6ef33-66b7-ff46-c572-4aaf5093e441.jpeg)


## 止めたプロセスを再開します。

```
redis-1:~# systemctl start redis-server
```

```
ord-haproxy2:/etc/systemd/system# systemctl start lelastic
```

![redis-sentinel-haproxy-ipsharing-7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/658578ce-fa0a-f92b-ef36-801807c36700.jpeg)


# まとめ
Redis Sentinel、HAProxy、IP Sharing を利用することで、高可用性の Redis サーバーを提供できます。利用するデータセンターの地域によって IP Sharing を利用したフェールオーバーの方式が変わることがありますので、ドキュメントを注意深く確認しましょう。