かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その19 )( Docker で Redis の環境を構築する2(Redis Cluster 構成))

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その18 )( Docker で Redis の環境を構築する(単体サーバ構成)+Spring Actuator の Endpoint の Basic 認証ではセッション情報を生成しないようにする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Docker で Redis Cluster 環境を構築します。

参照したサイト・書籍

  1. Redis 5 – bootstrapping a Redis Cluster with Docker
    https://simplydistributed.wordpress.com/2018/08/31/redis-5-bootstrapping-a-redis-cluster-with-docker/

  2. Redis cluster tutorial
    https://redis.io/topics/cluster-tutorial#creating-the-cluster

  3. A Redis Cluster of any size using Docker Compose and Redis 4.0 port-forwarding
    https://get-reddie.com/blog/redis4-cluster-docker-compose/

  4. プロダクションでRedis Clusterを3年間運用し続けた所感
    https://qiita.com/maruloop/items/a67b62d2a6a239c8fb67

  5. Spring Data Redis - 7. Redis Cluster
    https://docs.spring.io/spring-data/redis/docs/2.0.11.RELEASE/reference/html/#cluster

  6. Environment variables in Compose
    https://docs.docker.com/compose/environment-variables/

目次

  1. 方針
  2. コマンドラインから docker コマンドで Redis Cluster 環境を構築する
  3. ksbysample-webapp-lending から接続する + redis-cli で確認する
  4. Docker Compose で Redis Cluster 環境を構築する

手順

方針

  • Docker イメージは Docker Hub の redis:5.0.1 を利用します。
  • Docker コンテナで起動する Redis は、ポート番号を 6379、Cluster Bus ポート番号を 16379 にします(Redis Cluster ではポート番号 + 10000 のポート番号を Cluster Bus ポートとして使用します)。コンテナ側は 6379, 16379 固定です。
  • ホスト側では 6379, 6380, 6381, 6382, 6383, 6384 と 16379, 16380, 16381, 16382, 16383, 16384 の 12個のポート番号を Docker コンテナの Redis に紐づけます。
  • Docker コンテナで Redis を起動する時に以下のオプションを付けます。
    • --cluster-announce-ip 172.23.136.33(172.23.136.33 はホストの IP アドレスです)
    • --cluster-announce-port 6379(6379 はコンテナ毎に 6379, 6380, 6381, 6382, 6383, 6384 に変更します)
    • --cluster-announce-bus-port 16379(16379 はコンテナ毎に 16379, 16380, 16381, 16382, 16383, 16384 に変更します)

コマンドラインから docker コマンドで Redis Cluster 環境を構築する

docker-compose down コマンドを実行して起動している Docker コンテナを全て終了(削除)します。

以下のコマンドを実行して Docker コンテナで Redis サーバを起動します。

  • docker run -d --name redis-cluster-6379 -p 6379:6379 -p 16379:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6379 --cluster-announce-bus-port 16379
  • docker run -d --name redis-cluster-6380 -p 6380:6379 -p 16380:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6380 --cluster-announce-bus-port 16380
  • docker run -d --name redis-cluster-6381 -p 6381:6379 -p 16381:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6381 --cluster-announce-bus-port 16381
  • docker run -d --name redis-cluster-6382 -p 6382:6379 -p 16382:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6382 --cluster-announce-bus-port 16382
  • docker run -d --name redis-cluster-6383 -p 6383:6379 -p 16383:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6383 --cluster-announce-bus-port 16383
  • docker run -d --name redis-cluster-6384 -p 6384:6379 -p 16384:16379 redis:5.0.1 redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --cluster-announce-ip 172.23.136.33 --cluster-announce-port 6384 --cluster-announce-bus-port 16384

f:id:ksby:20181125221714p:plain

IntelliJ IDEA の Docker Plugin で Log を見ると以下のようになっています。

f:id:ksby:20181125223612p:plain

以下の redis-cli --cluster create コマンドで Redis Cluster を構築します。

  • docker run -i --rm redis:5.0.1 redis-cli --cluster create 172.23.136.33:6379 172.23.136.33:6380 172.23.136.33:6381 172.23.136.33:6382 172.23.136.33:6383 172.23.136.33:6384 --cluster-replicas 1

f:id:ksby:20181125222003p:plain f:id:ksby:20181125222236p:plain

IntelliJ IDEA の Docker Plugin で Log を見ると以下のようになっています。

f:id:ksby:20181125223754p:plain

動作確認します。docker exec -it redis-cluster-6379 redis-cli コマンドで redis-cluster-6379 のコンテナで redis-cli を起動した後、cluster nodes コマンドを実行すると master, slave の情報が表示されます。

f:id:ksby:20181125222535p:plain

redis-cli から set "test" "1234" を実行すると (error) MOVED 6918 172.23.136.33:6380 とホストの IPアドレスとポート番号で MOVED の応答が返ってきます。

f:id:ksby:20181125222749p:plain

今度は docker exec -it redis-cluster-6380 redis-cli コマンドで redis-cluster-6380 のコンテナで redis-cli を起動した後、set "test" "1234" を実行するとデータがセットされます。

f:id:ksby:20181125223123p:plain

flushdb コマンドでセットしたデータをクリアした後、docker exec -it redis-cluster-6379 redis-cli -c コマンドで redis-cluster-6379 のコンテナで redis-cli を起動した後 set "test" "1234" を実行すると、自動的に redis-cluster-6380 のコンテナの redis に接続してセットします。

f:id:ksby:20181127000456p:plain

ksbysample-webapp-lending から接続する + redis-cli で確認する

Redis Cluster に接続する場合、設定ファイルだけでは対応できません。Spring Data Redis - 7. Redis Cluster を参考に実装します。設定ファイルだけ設定した場合でも最初は動作するのですが、master の redis を落とした時に slave へ切り替えてくれません。

build.gradle の以下の点を変更します。

dependencies {
    ..........
    implementation("io.micrometer:micrometer-registry-prometheus")
    implementation("redis.clients:jedis")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    ..........
  • dependencies block に implementation("redis.clients:jedis") を追加します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

src/main/resources/application-develop.properties を以下のように変更します。

spring.redis.cluster.nodes[0]=172.23.136.33:6379
spring.redis.cluster.nodes[1]=172.23.136.33:6380
spring.redis.cluster.nodes[2]=172.23.136.33:6381
spring.redis.cluster.nodes[3]=172.23.136.33:6382
spring.redis.cluster.nodes[4]=172.23.136.33:6383
spring.redis.cluster.nodes[5]=172.23.136.33:6384
  • 以下の設定を削除します。
    • spring.redis.host=localhost
    • spring.redis.port=6379
  • 以下の設定を追加します。
    • spring.redis.cluster.nodes[0]=172.23.136.33:6379
    • spring.redis.cluster.nodes[1]=172.23.136.33:6380
    • spring.redis.cluster.nodes[2]=172.23.136.33:6381
    • spring.redis.cluster.nodes[3]=172.23.136.33:6382
    • spring.redis.cluster.nodes[4]=172.23.136.33:6383
    • spring.redis.cluster.nodes[5]=172.23.136.33:6384

src/main/java/ksbysample/webapp/lending/config/RedisClusterConfig.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.lending.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

import java.util.List;

/**
 * Redis Cluster 用 Configuration クラス
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConfig {

    List<String> nodes;

    /**
     * Redis Cluster に接続するための {@link JedisConnectionFactory} オブジェクトを生成する
     *
     * @return {@link JedisConnectionFactory} オブジェクト
     */
    @Bean
    public RedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory(new RedisClusterConfiguration(nodes));
    }

}

Tomcat を起動します。Spring Actuator の health check を見ると redis が認識されています。Redis Cluster 構成の場合、単体 Redis の時と表示が異なります。

f:id:ksby:20181127004224p:plain

ブラウザから http://localhost:8080/ のログイン画面にアクセスしてログインを試みると、問題なくログインできました。

redis-cli でデータを確認すると、データは保存されていますがクラスタ内のサーバに分散されていました。

f:id:ksby:20181127004416p:plain f:id:ksby:20181127004517p:plain f:id:ksby:20181127004616p:plain

PRINCIPAL_NAME_INDEX_NAME:tanaka.taro@sample.com" がセットされているコンテナ redis-cluster-6381 を停止します。

f:id:ksby:20181127005034p:plain f:id:ksby:20181127005125p:plain

ログイン中の画面でメニューから「貸出希望書籍登録」を選択すると問題なく画面が切り替わります(slave への切り替えが出来ないとエラーになります)。

f:id:ksby:20181127005213p:plain f:id:ksby:20181127005251p:plain

redis-cli でデータを確認すると、redis-cluster-6381 の slave になっていた redis-cluster-6383 に PRINCIPAL_NAME_INDEX_NAME:tanaka.taro@sample.com" のデータがセットされていました。

f:id:ksby:20181127005553p:plain f:id:ksby:20181127005652p:plain f:id:ksby:20181127005746p:plain

一旦作成した redis-cluster-XXXX のコンテナを全て削除します。

Docker Compose で Redis Cluster 環境を構築する

ホストのIPアドレス環境変数にセットして docker-compose.yml 内で利用できるようにするために、プロジェクトのルートディレクトリの下に .env というファイルを新規作成し、以下の内容を記述します。

HOST_IP_ADDRESS=172.23.136.33

redis-server 起動時のオプションでサーバ共通のものを定義するファイルを作成します。docker/redis の下に redis.conf というファイルを新規作成し、以下の内容を記述します。

port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

Redis Cluster の各サーバを起動するための Docker Image を作成するための Dockerfile を作成します。docker/redis の下に Dockerfile というファイルを新規作成し、以下の内容を記述します。

FROM redis:5.0.1
RUN apt-get update -qq && apt-get install -y expect
ADD redis.conf /etc/redis/
  • redis-cli --cluster create コマンドで Redis Cluster を構築する時に yes の入力を求められるところがあるので、expect をインストールして自動入力できるようにします。
  • ただし、いきなり apt-get install -y expect コマンドを実行しても失敗するので、その前に apt-get update -qq を実行します。
  • docker/redis/redis.conf を /etc/redis の下にコピーします。

docker-compose.yml に Redis Cluster を構築するための設定を記述します。redis-cluster-6379 ~ redis-cluster-6384 は redis-server 起動用コンテナで、redis-cluster-make は Redis Cluster を作成するための redis-cli --cluster create コマンド実行用コンテナです。また redis_exporter の REDIS_ADDR に redis://172.23.136.33:6379 ~ redis://172.23.136.33:6384 をカンマ区切りで列挙して全ての Redis Server のメトリックスを収集できるようにします。

  # 起動したコンテナに /bin/sh でアクセスする場合には以下のコマンドを実行する
  # docker exec -it redis /bin/sh
  #
  # 起動したコンテナの redis に redis-cli でアクセスするには以下のコマンドを実行する
  # docker exec -it redis redis-cli
  #
  #############################################################################
  # 単体 Redis サーバ
  # redis:
  #   image: redis:5.0.1
  #   container_name: redis
  #   ports:
  #   - "6379:6379"
  #
  #############################################################################
  # Redis Cluster
  redis-cluster-6379:
    build: ./docker/redis
    image: redis:5.0.1-custom
    container_name: redis-cluster-6379
    ports:
    - "6379:6379"
    - "16379:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6379 \
                  --cluster-announce-bus-port 16379
  redis-cluster-6380:
    image: redis:5.0.1-custom
    container_name: redis-cluster-6380
    ports:
    - "6380:6379"
    - "16380:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6380 \
                  --cluster-announce-bus-port 16380
    depends_on:
    - redis-cluster-6379
  redis-cluster-6381:
    image: redis:5.0.1-custom
    container_name: redis-cluster-6381
    ports:
    - "6381:6379"
    - "16381:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6381 \
                  --cluster-announce-bus-port 16381
    depends_on:
    - redis-cluster-6379
  redis-cluster-6382:
    image: redis:5.0.1-custom
    container_name: redis-cluster-6382
    ports:
    - "6382:6379"
    - "16382:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6382 \
                  --cluster-announce-bus-port 16382
    depends_on:
    - redis-cluster-6379
  redis-cluster-6383:
    image: redis:5.0.1-custom
    container_name: redis-cluster-6383
    ports:
    - "6383:6379"
    - "16383:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6383 \
                  --cluster-announce-bus-port 16383
    depends_on:
    - redis-cluster-6379
  redis-cluster-6384:
    image: redis:5.0.1-custom
    container_name: redis-cluster-6384
    ports:
    - "6384:6379"
    - "16384:16379"
    command:
    - /bin/sh
    - -c
    - |
      redis-server /etc/redis/redis.conf \
                  --cluster-announce-ip ${HOST_IP_ADDRESS} \
                  --cluster-announce-port 6384 \
                  --cluster-announce-bus-port 16384
    depends_on:
    - redis-cluster-6379
  redis-cluster-make:
    image: redis:5.0.1-custom
    container_name: redis-cluster-make
    command:
    - /bin/sh
    - -c
    - |
      expect -c "
      spawn redis-cli --cluster create \
                        ${HOST_IP_ADDRESS}:6379 \
                        ${HOST_IP_ADDRESS}:6380 \
                        ${HOST_IP_ADDRESS}:6381 \
                        ${HOST_IP_ADDRESS}:6382 \
                        ${HOST_IP_ADDRESS}:6383 \
                        ${HOST_IP_ADDRESS}:6384 \
                      --cluster-replicas 1
      expect \"Can I set the above configuration? (type 'yes' to accept): \"
      send \"yes\n\"
      expect eof
      "
    depends_on:
    - redis-cluster-6379
    - redis-cluster-6380
    - redis-cluster-6381
    - redis-cluster-6382
    - redis-cluster-6383
    - redis-cluster-6384

  redis_exporter:
    image: oliver006/redis_exporter:latest
    container_name: redis_exporter
    ports:
    - "9121:9121"
    environment:
    - REDIS_ADDR=redis://${HOST_IP_ADDRESS}:6379,redis://${HOST_IP_ADDRESS}:6380,redis://${HOST_IP_ADDRESS}:6381,redis://${HOST_IP_ADDRESS}:6382,redis://${HOST_IP_ADDRESS}:6383,redis://${HOST_IP_ADDRESS}:6384

これで全ての準備が整いましたので docker-compose up -d コマンドを実行します。最初に redis:5.0.1-custom イメージを生成した後、各コンテナが生成されます。

f:id:ksby:20181130061648p:plain (.....途中は長いので省略.....) f:id:ksby:20181130061833p:plain

IntelliJ IDEA の docker plugin で見ると、各コンテナは起動しており redis-cluster-make や redis-cluster-6379 コンテナのログは以下のように出力されています(yes が自動入力されています)。

f:id:ksby:20181130062721p:plain f:id:ksby:20181130062824p:plain

redis-cluster-6379 と redis-cluster-6384 コンテナで redis-cli コマンドを起動して cluster nodes コマンドを実行すると Redis Cluster が構築されていることが分かります。

f:id:ksby:20181130063039p:plain

Tomcat を起動して Spring Actuator の health check を見ると先程と同様に redis が cluster と認識されて表示されて、

f:id:ksby:20181130063457p:plain

http://localhost:8080/ にアクセスしてログイン画面を表示してからログインした後、

f:id:ksby:20181130063603p:plain f:id:ksby:20181130063656p:plain

各 redis コンテナで redis-cli を起動して keys * コマンドを実行するとセッション情報が保存されていることが確認できます。

f:id:ksby:20181130063900p:plain

Grafana からも各 redis サーバのメトリックスが確認できます。

f:id:ksby:20181130064330p:plain

docker-compose down コマンドを実行すると、全てのコンテナが停止・削除されます。

f:id:ksby:20181130064606p:plain f:id:ksby:20181130064711p:plain

次から docker-compose up -d コマンドで起動する時は、redis:5.0.1-custom イメージが生成されているので1回目より短い時間で起動できます。

f:id:ksby:20181130065315p:plain

これで簡単に Redis Cluster が起動できる環境が出来ました。

履歴

2018/11/30
初版発行。