Redis ClusterをDockerで試す


この記事は [MicroAd Advent Calendar 2021](https://qiita.com/advent-calendar/2021/microad) の11日目の記事です。

# 概要

本記事では，Docker環境でRedis Clusterを構築し，動作確認するまでの手順について記します．
理解の整理を兼ねたアウトプットのため，至らぬ部分もあるかと思いますがご容赦ください．
なお，本記事ではRedis自体の説明や，Replicationに関する内容は省略しています．

## DockerでRedis環境の準備

以下のようなディレクトリ構成は作成します．

```
❯ tree
.
├── conf
│   └── redis.conf
├── conf2
│   └── redis.conf
├── conf3
│   └── redis.conf
├── conf4
│   └── redis.conf
└── docker-compose.yml

4 directories, 5 files
```

redis.confは以下のようにクラスタ機能をONにする内容だけを記述します．
他にもRedisには様々な設定がありますが，分かりやすくまとまった記事も多いため[^1]，本記事では最低限の設定のみにし，デフォルト設定を前提として進めます．

[^1]: https://zenn.dev/tayura/articles/dfc8be8418d927

```redis.conf
cluster-enabled yes
```

redisは公式のDockerイメージ[^2]を利用します．
RedisのDockerイメージでは`command`に設定ファイルのパスを指定すると起動時に読み込むので，
`volumes`で設定ファイルをマウントし，コンテナ内のパスに合わせたパスを`command`で指定します．
`depends_on`はサービス名が`docker{01-04}`と連番にしているので，IPアドレスも合わせて連番で付与されるように起動順を合わせるために記述しています(後でIPアドレスを調べる際に見やすくなります)．


[^2]: https://hub.docker.com/_/redis


```docker-compose.yml
version: '3'
services:
  redis01:
    image: redis:6.2.6
    volumes:
      - "./conf:/etc/redis"
    command: "/etc/redis/redis.conf"
  redis02:
    image: redis:6.2.6
    volumes:
      - "./conf2:/etc/redis"
    command: "/etc/redis/redis.conf"
    depends_on:
      - redis01
  redis03:
    image: redis:6.2.6
    volumes:
      - "./conf3:/etc/redis"
    command: "/etc/redis/redis.conf"
    depends_on:
      - redis02
  redis04:
    image: redis:6.2.6
    volumes:
      - "./conf4:/etc/redis"
    command: "/etc/redis/redis.conf"
    depends_on:
      - redis03
```

Dockerコンテナを起動させます．

```
❯ docker-compose up -d
Building with native build. Learn about native build in Compose here: https://docs.docker.com/go/compose-native-build/
Creating network "cluster-test_default" with the default driver
Creating cluster-test_redis01_1 ... done
Creating cluster-test_redis02_1 ... done
Creating cluster-test_redis03_1 ... done
Creating cluster-test_redis04_1 ... done
```

redis01のDocker環境のBashに入って作業をしていきます．

まず最初に`redis-cli`コマンドで各RedisはIPアドレスで指定する必要があるため，IPアドレスを調べます．
`docker inspect`コマンドで調べてもいいですが，今回は`dig`で調べることにします．


```
❯ docker-compose exec redis01 bash
root@6ba9e07d9434:/data# apt-get update
Get:1 http://deb.debian.org/debian bullseye InRelease [116 kB]
...略

root@6ba9e07d9434:/data# apt install dnsutils
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
...略

root@6ba9e07d9434:/data# seq 1 4 | xargs -i dig redis0{} | grep redis
; <<>> DiG 9.16.22-Debian <<>> redis01
;redis01.                       IN      A
redis01.                600     IN      A       192.168.144.2
; <<>> DiG 9.16.22-Debian <<>> redis02
;redis02.                       IN      A
redis02.                600     IN      A       192.168.144.3
; <<>> DiG 9.16.22-Debian <<>> redis03
;redis03.                       IN      A
redis03.                600     IN      A       192.168.144.4
; <<>> DiG 9.16.22-Debian <<>> redis04
;redis04.                       IN      A
redis04.                600     IN      A       192.168.144.5
```

以上で4台のRedisが起動し，IPアドレスが分かった状態になりました．

```
redis01 = 192.168.144.2
redis02 = 192.168.144.3
redis03 = 192.168.144.4
redis04 = 192.168.144.5
```

## Redis Clusterの構築

`redis-cli --cluster`コマンドでクラスタを作成します．

クラスタ作成では，Slotの設定をする必要があります．
Slotとはデータをクラスタ内のどのノードに格納するかを決めるための仕組みです．
データ格納時に0~16383番のいずれかに割り当てられ，割り当てられたSlotを持つノードにデータは格納されることで水平分割(シャーディング)されます．


```
root@6ba9e07d9434:/data# redis-cli --cluster create 192.168.144.2:6379 192.168.144.3:6379 192.168.144.4:6379
>>> Performing hash slots allocation on 3 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
M: 0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379
   slots:[0-5460] (5461 slots) master
M: 5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379
   slots:[5461-10922] (5462 slots) master
M: 5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379
   slots:[10923-16383] (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes # <-- 3ノードで均等に分割するか聞かれます
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 192.168.144.2:6379)
M: 0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379
   slots:[0-5460] (5461 slots) master
M: 5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379
   slots:[10923-16383] (5461 slots) master
M: 5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
```

クラスタの状態は`redis-cli cluster nodes`コマンドから確認できます．
`-h`オプションでホストを指定することができ，以下ではホストredis01を指定しています．
3ノードがmaster，接続しているホストにはmyselfと表記さています．

```
root@6ba9e07d9434:/data# redis-cli -h redis01 cluster nodes
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639150635000 3 connected 10923-16383
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 myself,master - 0 1639150632000 1 connected 0-5460
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 master - 0 1639150635554 2 connected 5461-10922
```

## データの格納と取得

clusterへの接続は`-c`オプションを付与します．これにより格納先のSlotが異なる場合に自動でリダイレクトしてくれます．

```
root@6ba9e07d9434:/data# redis-cli -h redis01 -c
redis01:6379> 
redis01:6379> set key01 value01
-> Redirected to slot [13770] located at 192.168.144.4:6379
OK
192.168.144.4:6379> get key01
"value01"
```

`-c`オプションなしで確認してみます．
まずデータが格納されてないredis01(192.168.144.2)からGETするとエラーになります．
そこでエラーで表記されたredis03(192.168.144.4)に接続するとGETに成功します．

```
root@6ba9e07d9434:/data# redis-cli -h redis01   
redis01:6379> get key01
(error) MOVED 13770 192.168.144.4:6379
redis01:6379> 
root@6ba9e07d9434:/data# redis-cli -h redis03 
redis03:6379> get key01
"value01"
```

## スレーブ追加

redis04(192.168.144.5:6379)をredis0(192.168.144.2)にスレーブとして追加します．
コマンドは`redis-cli --cluster add-node <追加するノードIP:Port> <追加先のノードIP:Port>`の形式で，`-cluster-slave`を付けるとスレーブ，付けないとマスターとして追加になります．

```
root@6ba9e07d9434:/data# redis-cli --cluster add-node 192.168.144.5:6379 192.168.144.2:6379 --cluster-slave
>>> Adding node 192.168.144.5:6379 to cluster 192.168.144.2:6379
>>> Performing Cluster Check (using node 192.168.144.2:6379)
M: 0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379
   slots:[0-5460] (5461 slots) master
M: 5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379
   slots:[10923-16383] (5461 slots) master
M: 5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Automatically selected master 192.168.144.2:6379
>>> Send CLUSTER MEET to node 192.168.144.5:6379 to make it join the cluster.
Waiting for the cluster to join

>>> Configure node as replica of 192.168.144.2:6379.
[OK] New node added correctly.
```

確認すると一番下にスレーブが追加されています．
スレーブの一番右の文字列はマスターのノードIDとなっており，`0bd...`はredis0(192.168.144.2)を指しています．

```
root@6ba9e07d9434:/data# redis-cli cluster nodes
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639152407000 3 connected 10923-16383
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 myself,master - 0 1639152406000 1 connected 0-5460
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 master - 0 1639152408449 2 connected 5461-10922
e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379@16379 slave 0bd346a134981afb74a136e665a6027821b2016b 0 1639152407444 1 connected
```

以下のコマンドではどのマスターにスレーブやキーがいくつかあるかを確認できます．
スレーブが1台追加されているマスターがあることが分かります．

```
root@6ba9e07d9434:/data# redis-cli --cluster info 192.168.144.2 6379
192.168.144.2:6379 (0bd346a1...) -> 0 keys | 5461 slots | 1 slaves.
192.168.144.4:6379 (5549145f...) -> 1 keys | 5461 slots | 0 slaves.
192.168.144.3:6379 (5dbd6568...) -> 0 keys | 5462 slots | 0 slaves.
[OK] 1 keys in 3 masters.
0.00 keys per slot on average.
```

failoverコマンドでは，マスターとスレーブを入れ替えることができます．

```
root@6ba9e07d9434:/data# redis-cli -h 192.168.144.5 cluster failover
OK
```

redis01(192.168.144.2)がスレーブに，redis04(192.168.144.5)がマスターに入れ替わりました．

```
root@6ba9e07d9434:/data# redis-cli cluster nodes
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639155508000 3 connected 10923-16383
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 myself,slave e686a6bb888858f330218ed8c4355ef481028668 0 1639155511000 4 connected
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 master - 0 1639155511870 2 connected 5461-10922
e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379@16379 master - 0 1639155510865 4 connected 0-5460
```

動作が確認できたので，スレーブのredis01(192.168.144.2)を削除します．
ノードの削除は`redis-cli --cluster del-node <削除対象のノードのIP:Port> <削除対象のノードID>`

```
root@6ba9e07d9434:/data# redis-cli --cluster del-node 192.168.144.2:6379 0bd346a134981afb74a136e665a6027821b2016b
>>> Removing node 0bd346a134981afb74a136e665a6027821b2016b from cluster 192.168.144.2:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
```

クラスタ情報を見るとredis01(192.168.144.2)は消えています．

```
root@6ba9e07d9434:/data# redis-cli -h redis02 cluster nodes
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639155750324 3 connected 10923-16383
e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379@16379 master - 0 1639155749319 4 connected 0-5460
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 myself,master - 0 1639155749000 2 connected 5461-10922
```

削除したノードredis01(192.168.144.2)から見ると自身しか見れなくなっています．

```
root@6ba9e07d9434:/data# redis-cli -h redis01 cluster nodes
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 myself,master - 0 1639155731000 1 connected
```

## マスター追加

次はredis01(192.168.144.2)をマスターとして追加します．

```
root@6ba9e07d9434:/data# redis-cli --cluster add-node 192.168.144.2:6379 192.168.144.5:6379
>>> Adding node 192.168.144.2:6379 to cluster 192.168.144.5:6379
>>> Performing Cluster Check (using node 192.168.144.5:6379)
M: e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379
   slots:[0-5460] (5461 slots) master
M: 5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379
   slots:[5461-10922] (5462 slots) master
M: 5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379
   slots:[10923-16383] (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.144.2:6379 to make it join the cluster.
[OK] New node added correctly.
```

マスター4台になっていることが確認できます．
しかし，追加したノードにはSlotが割り当てられていないため，`connected`の右には何も書かれていません．

```
root@6ba9e07d9434:/data# redis-cli -h redis02 cluster nodes
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 master - 0 1639155981275 1 connected
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639155980271 3 connected 10923-16383
e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379@16379 master - 0 1639155979267 4 connected 0-5460
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 myself,master - 0 1639155978000 2 connected 5461-10922
```

`reshard`コマンドでSlotを再配置することができます．

コマンドは対話的に進み，最初に移動するSlottt数を聞かれるので，
16384(全Slot数)/4(マスター数)= 4096 のため，`4096`と入力を入力します．
次に全ノードから移動ため`all`を，実行するか聞かれるので `yes`を入力します．


```
root@6ba9e07d9434:/data# redis-cli --cluster reshard 192.168.144.2:6379
>>> Performing Cluster Check (using node 192.168.144.2:6379)
M: 0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379
   slots: (0 slots) master
M: 5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379
   slots:[10923-16383] (5461 slots) master
M: 5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379
   slots:[5461-10922] (5462 slots) master
M: e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379
   slots:[0-5460] (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 4096   <-- 1. 移動Slot数
What is the receiving node ID? 0bd346a134981afb74a136e665a6027821b2016b
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: all  <-- 2. 全ノードから移動
...略

    Moving slot 1363 from e686a6bb888858f330218ed8c4355ef481028668
    Moving slot 1364 from e686a6bb888858f330218ed8c4355ef481028668
Do you want to proceed with the proposed reshard plan (yes/no)? yes  <-- 3. 上記変更で実行するか

....略
Moving slot 1363 from 192.168.144.5:6379 to 192.168.144.2:6379: 
Moving slot 1364 from 192.168.144.5:6379 to 192.168.144.2:6379:
```

クラスタのノードを確認すると均等にSlotが水平分割されていることが分かります．

```
root@6ba9e07d9434:/data# redis-cli -h redis02 cluster nodes
0bd346a134981afb74a136e665a6027821b2016b 192.168.144.2:6379@16379 master - 0 1639157313371 5 connected 0-1364 5461-6826 10923-12287
5549145f997afaad00dad89b5f312a044be6c5ca 192.168.144.4:6379@16379 master - 0 1639157315379 3 connected 12288-16383
e686a6bb888858f330218ed8c4355ef481028668 192.168.144.5:6379@16379 master - 0 1639157314374 4 connected 1365-5460
5dbd6568e5ec7b2c15e4d5bb4988726c663fcf75 192.168.144.3:6379@16379 myself,master - 0 1639157314000 2 connected 6827-10922
```

# まとめ

本記事では，Redis Clusterの動作をDocker環境を使って構築し，Clusterとしての動作確認をしました．

Dockerを使わない場合はRedisにバインドさせるポートをプロセス毎に変えてRedisを起動させることも可能です[^3]．
Dockerを使うことで環境構築の手間がなく，手軽に動作確認できると思い，今回記事を執筆しました．

少しでも理解の助けになれれば幸いです．

[^3]: https://qiita.com/keitatata/items/44678ad472e61a4606c5