CLOVER🍀

That was when it all began.

Docker Composeで、Redis Clusterをできる限り簡単に作る

これは、なにをしたくて書いたもの?

Redisでたまにクラスタを作るのですが、ふだん素のDockerでやっていて面倒だなーと思うようになり。

Docker Composeで構成しようと思うのですが、Redis Clusterを作る時はコマンドを実行する必要もあり。

このあたり、どうしようかな?と試行してみた結果です。

環境

今回の環境は、こちらです。

$ docker-compose -v
docker-compose version 1.25.5, build 8a1c60f6


$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 01:25:46 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 01:24:19 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

RedisnのDockerイメージは、DockerHubにあるオフィシャルイメージをベースにします。

redis

今回使うのは、Redis 5.0.8とします。

Docker Composeの用意する

まずは、Docker Compose用のディレクトリを作成。

$ mkdir redis-cluster
$ cd redis-cluster

docker-compose.ymlを作成します。RedisのDockerイメージをそのままRedis Clusterには使えないので、ビルドも行うことにします。
docker-compose.yml

version: "3.8"
services:
  redis:
    build: .
    image: kazuhira/redis-cluster:5.0.8
    networks:
      - redis_network

networks:
  redis_network:

用意したDockerfile。RedisのオフィシャルDockerイメージをベースに、カスタマイズ。
Dockerfile

FROM redis:5.0.8

COPY redis.conf /usr/local/etc/redis/redis.conf
RUN chown -R redis.redis /usr/local/etc/redis

CMD  ["redis-server", "/usr/local/etc/redis/redis.conf"]

もともとENTRYPOINTが設定されているので、こちらの流れに乗ります。

https://github.com/docker-library/redis/blob/master/5.0/Dockerfile#L98-L102

COPYで追加している、Redisの設定ファイル。Redis Clusterを構成するためのミニマムです。
redis.conf

cluster-enabled yes
cluster-config-file /usr/local/etc/redis/nodes.conf
cluster-node-timeout 5000
appendonly yes

では、コンテナを起動。

$ docker-compose up --scale redis=6

6つ、コンテナが起動しました。

$ docker-compose ps
        Name                       Command               State    Ports  
-------------------------------------------------------------------------
redis-cluster_redis_1   docker-entrypoint.sh redis ...   Up      6379/tcp
redis-cluster_redis_2   docker-entrypoint.sh redis ...   Up      6379/tcp
redis-cluster_redis_3   docker-entrypoint.sh redis ...   Up      6379/tcp
redis-cluster_redis_4   docker-entrypoint.sh redis ...   Up      6379/tcp
redis-cluster_redis_5   docker-entrypoint.sh redis ...   Up      6379/tcp
redis-cluster_redis_6   docker-entrypoint.sh redis ...   Up      6379/tcp

ただ、まだ単独でRedisが浮いているだけです。

Redis Clusterを構成する

ここから、Redis Clusterを構成します。

起動したRedisを、一気にクラスタに含めたいと思うので、シェルスクリプトを書きましょう。
create-cluster.sh

#!/bin/bash

REDIS_NETWORK=redis-cluster_redis_network
REDIS_SERVICE_NAME=redis

REDIS_PORT=6379
CLUSTER_REPLICAS=1

NODES=`docker network inspect ${REDIS_NETWORK} | jq '.[0].Containers | .[].IPv4Address' | perl -wp -e 's!"(.+)/.+"\r?\n!$1:6379 !g'`

docker-compose exec ${REDIS_SERVICE_NAME} bash -c "yes yes | redis-cli --cluster create ${NODES} --cluster-replicas ${CLUSTER_REPLICAS}"

これで、docker-compose.ymlで定義されたネットワーク内にいるRedisを、全部クラスタに参加させます。

レプリカ数はCLUSTER_REPLICASで1にしているので、最低6ノード必要です。レプリカ数を0にすれば、3ノードでOKですね。

実行権限を付与して

$ chmod a+x create-cluster.sh

実行。

$ ./create-cluster.sh

クラスタが構成されました。

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.19.0.3:6379 to 172.19.0.5:6379
Adding replica 172.19.0.6:6379 to 172.19.0.2:6379
Adding replica 172.19.0.4:6379 to 172.19.0.7:6379
M: 85961312c8f48c18cce5ff1e15de7a72a52bad9a 172.19.0.5:6379
   slots:[0-5460] (5461 slots) master
M: ee7e91e81d29311080f27e39659cf090334b7cd7 172.19.0.2:6379
   slots:[5461-10922] (5462 slots) master
M: 0b2a286562de2583f42534c90d04ccd191147c75 172.19.0.7:6379
   slots:[10923-16383] (5461 slots) master
S: 91c6b115bd237f64462922bac0ba3ec78ac04a07 172.19.0.4:6379
   replicates 0b2a286562de2583f42534c90d04ccd191147c75
S: dc8b13f9b5b1cbcbf5a25277fa68f721a4303a13 172.19.0.3:6379
   replicates 85961312c8f48c18cce5ff1e15de7a72a52bad9a
S: 467f1f8eb26d8de160589a3f86cc1e57f3295130 172.19.0.6:6379
   replicates ee7e91e81d29311080f27e39659cf090334b7cd7
Can I set the above configuration? (type 'yes' to accept): >>> 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 172.19.0.5:6379)
M: 85961312c8f48c18cce5ff1e15de7a72a52bad9a 172.19.0.5:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 91c6b115bd237f64462922bac0ba3ec78ac04a07 172.19.0.4:6379
   slots: (0 slots) slave
   replicates 0b2a286562de2583f42534c90d04ccd191147c75
M: ee7e91e81d29311080f27e39659cf090334b7cd7 172.19.0.2:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 0b2a286562de2583f42534c90d04ccd191147c75 172.19.0.7:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: dc8b13f9b5b1cbcbf5a25277fa68f721a4303a13 172.19.0.3:6379
   slots: (0 slots) slave
   replicates 85961312c8f48c18cce5ff1e15de7a72a52bad9a
S: 467f1f8eb26d8de160589a3f86cc1e57f3295130 172.19.0.6:6379
   slots: (0 slots) slave
   replicates ee7e91e81d29311080f27e39659cf090334b7cd7
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

「cluster nodes」で、クラスタの状態を見てみます。

$ docker-compose exec redis redis-cli cluster nodes
ee7e91e81d29311080f27e39659cf090334b7cd7 172.19.0.2:6379@16379 master - 0 1587192311927 2 connected 5461-10922
0b2a286562de2583f42534c90d04ccd191147c75 172.19.0.7:6379@16379 master - 0 1587192311000 3 connected 10923-16383
85961312c8f48c18cce5ff1e15de7a72a52bad9a 172.19.0.5:6379@16379 master - 0 1587192311000 1 connected 0-5460
91c6b115bd237f64462922bac0ba3ec78ac04a07 172.19.0.4:6379@16379 myself,slave 0b2a286562de2583f42534c90d04ccd191147c75 0 1587192311000 4 connected
467f1f8eb26d8de160589a3f86cc1e57f3295130 172.19.0.6:6379@16379 slave ee7e91e81d29311080f27e39659cf090334b7cd7 0 1587192312000 6 connected
dc8b13f9b5b1cbcbf5a25277fa68f721a4303a13 172.19.0.3:6379@16379 slave 85961312c8f48c18cce5ff1e15de7a72a52bad9a 0 1587192311000 1 connected

master、slaveがそれぞれ3ノードずつありますね。

シェルスクリプトで実行しているのは、「docker network inspect」の結果を利用しています。コンテナ名とIPアドレスを表示するには、
jqを使って以下のような感じで。

$ docker network inspect redis-cluster_redis_network | jq '.[0].Containers | .[] | {Name, IPv4Address}'
{
  "Name": "redis-cluster_redis_4",
  "IPv4Address": "172.19.0.5/16"
}
{
  "Name": "redis-cluster_redis_6",
  "IPv4Address": "172.19.0.2/16"
}
{
  "Name": "redis-cluster_redis_5",
  "IPv4Address": "172.19.0.7/16"
}
{
  "Name": "redis-cluster_redis_1",
  "IPv4Address": "172.19.0.4/16"
}
{
  "Name": "redis-cluster_redis_3",
  "IPv4Address": "172.19.0.3/16"
}
{
  "Name": "redis-cluster_redis_2",
  "IPv4Address": "172.19.0.6/16"
}

IPアドレスのみ。

$ docker network inspect redis-cluster_redis_network | jq '.[0].Containers | .[].IPv4Address'
"172.19.0.5/16"
"172.19.0.2/16"
"172.19.0.7/16"
"172.19.0.4/16"
"172.19.0.3/16"
"172.19.0.6/16"

コンテナのみ。

$ docker network inspect redis-cluster_redis_network | jq '.[0].Containers | .[].Name'
"redis-cluster_redis_4"
"redis-cluster_redis_6"
"redis-cluster_redis_5"
"redis-cluster_redis_1"
"redis-cluster_redis_3"
"redis-cluster_redis_2"

この後のノード追加、削除などは、「redis-cli --cluster」でDocker Compose越しに操作すると良いでしょう。

$ docker-compose exec redis redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

この時に、各コンテナのIPアドレスを使います。

たとえば、コンテナをスケールさせて

$ docker-compose scale redis=12

ノード追加。

$ docker-compose exec redis redis-cli --cluster add-node 172.19.0.8:6379 172.19.0.2:6379
$ docker-compose exec redis redis-cli --cluster add-node 172.19.0.9:6379 172.19.0.2:6379
$ docker-compose exec redis redis-cli --cluster add-node 172.19.0.10:6379 172.19.0.2:6379
...
...

このあとは、reshardとrebalanceが必要ですね(ここまででやめた)。

とまあ、こんな感じで。

参考)

GitHub - Grokzen/redis-py-cluster: Python cluster client for the official redis cluster. Redis 3.0+.