Redis+Sentinel を Kubernetes で動かしたメモ

## Redis+Sentinelを構成する理由

Redisキャッシュサーバーのアクセスの集中を軽減して、スケールするために、読書き可能なRedisマスターと読取り専用のRedisスレーブを組み合わせた構成を取ることがあります。これを Kubernetes上で実現する方法について、確認したのでメモとして残します。今更、HelmやOperatorで構築さればいいじゃない？ と言われるかもしれませんが、Redisで高可用性を組む時のRedisの内容を理解しておきたいと思います。

![redis-master-slave.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/d572e85f-4871-42be-2b33-3251775e46c5.png)

IKS(IBM Cloud Kubernetes Service) https://cloud.ibm.com/ のK8sクラスタ上で動かしたのですが、使用しているマニフェストは、クラウド中立なマニフェストなので、どのクラウドでも動作すると思います。


Redisの監視とマスター選出の機能をになったSentinelが、Redisのプロジェクトから提供されています。Sentinelも可用性とマスター選出投票のために、複数プロセスが機能するようになっています。

Sentinelは、全てのRedisを監視しておき、マスターの停止に対して、次のマスターを選出します。そして、Sentinelに問い合わせることで、その時点でのマスターを教えてくれます。もちろん、Slaveも知ることができます。

Redisのクライアントであるアプリは、Sentinelに問い合わせて、RedisマスターのIPアドレスを求め、Redisのマスターへ書き込みます。また、読取りでは、スレーブをアクセスして、マスターが過負荷になることを回避します。Sentinelに問い合わせるための、プログラム言語のクライアントライブラリは、参考資料 にリンクを挙げておきます。

![redis-sentinel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/eb3a5879-37b9-7b26-b33c-f7c8d943f651.png)


## Redis + Sentinel の高可用性構成を起動する。

それでは、上図の構成をKubernetes上で作っていきます。 

Kubernetesのクラスタが作られ、kubectlコマンドで操作できる状態を想定して、書いていきます。
それから、この構成は、参考資料 1 を、そのまま利用しています。 ここではフォークしたリポジトリをクローンしています。

~~~
git clone https://github.com/takara9/examples
cd examples/staging/storage/redis

# 起動用 Redisマスター + Sentinel のポッドをスタート
kubectl create -f redis-master.yaml

# Sentinelのサービスを起動　 Sentinelにリクエストする時は、これにアクセスする
kubectl create -f redis-sentinel-service.yaml

# Redisサーバーを、レプリケーションコントローラー下で起動する
kubectl create -f redis-controller.yaml

# Sentinelサーバーも、レプリケーションコントローラー下で起動する
kubectl create -f redis-sentinel-controller.yaml

# レプリカ数を増やして、冗長化と性能アップを実施
kubectl scale rc redis --replicas=3
kubectl scale rc redis-sentinel --replicas=3
~~~


起動後に、Sentinelのサービス名とIPアドレスを確認しておきます。

~~~
$ kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
kubernetes       ClusterIP   172.21.0.1       <none>        443/TCP     4d8h
redis-sentinel   ClusterIP   172.21.195.107   <none>        26379/TCP   92s
~~~

さらに、RedisサーバーとSentinelのポッドのIPアドレス付きリストを確認する。

~~~
i$ kubectl get po -o wide
NAME                   READY   STATUS   AGE     IP               NODE
redis-d62nc            1/1     Running  5m28s   172.30.94.152    10.193.10.14
redis-master           2/2     Running  6m2s    172.30.222.152   10.193.10.58
redis-sentinel-f49jh   1/1     Running  5m37s   172.30.94.150    10.193.10.14
redis-sentinel-m6dq8   1/1     Running  5m20s   172.30.222.153   10.193.10.58
redis-sentinel-p4jtb   1/1     Running  5m20s   172.30.94.153    10.193.10.14
redis-vnmsq            1/1     Running  5m28s   172.30.94.151    10.193.10.14
~~~


## Sentinelに繋いでRedisマスターを求めて、Redisマスターに、テスト用データを書き込む

アプリケーションのクライアント相当としてredis-cliで、Redis+Sentinelの構成へ繋ぎます。

~~~
$ kubectl run -it redis-cli --rm --image redis --restart=Never -- bash
If you don't see a command prompt, try pressing enter.
root@redis-cli:/data# 
~~~

Sentinelへ繋いで、RedisマスターのIPアドレスを求めます。Sentinelのコマンドは参考資料 2 にあります。

~~~
root@redis-cli:/data# redis-cli -h redis-sentinel -p 26379
redis-sentinel:26379> SENTINEL get-master-addr-by-name mymaster
1) "172.30.222.152"
2) "6379"
~~~


Redisマスターからスレーブへのデータ同期を確認するために、RedisマスターにKeyとValueをセットします。

~~~
redis-sentinel:26379> connect 172.30.222.152 6379
172.30.222.152:6379> set abc 1000
OK
172.30.222.152:6379> get abc
"1000"
172.30.222.152:6379> incr abc
(integer) 1001
172.30.222.152:6379> incr abc
(integer) 1002
~~~

Redisスレーブをリストして、IPアドレスとポート番号を確認します。

~~~
redis-sentinel:26379> SENTINEL slaves mymaster
1)  1) "name"
    2) "172.30.94.151:6379"
<中略>
2)  1) "name"
    2) "172.30.94.152:6379"   
~~~

スレーブに繋いで、データが同期されているか確認します。目論見通りにデータが取り出せます。

~~~
172.30.222.152:6379> connect 172.30.94.151 6379
172.30.94.151:6379> get abc
"1002"
172.30.94.151:6379> connect 172.30.94.152 6379
172.30.94.152:6379> get abc
"1002"
~~~


## マスター停止のテスト

RedisマスターのIPアドレスを調べて、kubectlコマンドで、マスターとなっているポッドを削除して、
フェイルオーバーを確認します。

~~~
redis-sentinel:26379> SENTINEL get-master-addr-by-name mymaster
1) "172.30.222.152"
2) "6379"
~~~

別のターミナルから、マスターのポッドを削除します。 
起動時に、ポッドだけで立ち上げたものですが、後から追加したレプリケーションコントローラーのセレクターのラベルと同じにしているため、レプリケーションコントローラーは、このポッドも管理下として見なし、削除されると、新たなポッドを起動して、レプリカ数を維持するように振舞います。

~~~
$ kubectl delete po redis-master
pod "redis-master" deleted
~~~

レプリケーションコントローラーから起動されたポッドは、Redisスレーブとして参加します。そして、Sentinelの働きで、マスターに昇格するために投票が行われ、新たなRedisマスターが決まります。

~~~
redis-sentinel:26379> SENTINEL get-master-addr-by-name mymaster
1) "172.30.94.156"
2) "6379"
~~~

## まとめ

既にビルドしたコンテナを使っているので、本番利用を考えるには、コンテナのビルドから考えないといけません。これに対して、参考資料1のGitHubに含まれています。
マスタースレーブ方式のスケール可能なキャッシュサーバを Kubernetes上で利用できることが、確認できました。



## 参考資料

1. k8s redis例題, https://github.com/kubernetes/examples/tree/master/staging/storage/redis
2. センチネルクライアント, https://redis.io/topics/sentinel-clients
3. PHP Redisクライアントライブラリ、https://github.com/jamescauwelier/PSRedis
4. Java Redisクライアントライブラリ、https://github.com/redisson/redisson