今回は「Kubernetes上にRedisのSentinel構成を構築する方法」についてまとめます。
今回はRedisの冗長化構成3種(Replication、Sentinel、Cluster)のうち「Sentinel」構成について、構築する方法をまとめました。 ただ…残念ながら現状のRedis(v5.0.5)では標準付属の Sentinel のみを利用した Kubernetes上での"完全な" 冗長構成は作れなさそうです。 もし完全な冗長構成を作りたい場合は少し自作が必要そうです。
概要
今回は以下のような構成で Redis Sentinel 構成を組み上げていきます。
構成としては「マスター1台、スレーブ1台、センチネル3台」で、「パスワードを共有」するような構成にしています。
「Redis Seerver の StatefulSet に Sentinel を同梱」するような実装だと Pod 内に Redis Server と Sentinel が一緒になってしまってPodの再起動がしづらくなってしまいます。 まぁ、そもそも役割違うのでPod分けてあげるのが妥当かと思って上のような構成にしています。
なお、Sentinel側もStatefulSetで構築していますが、こちらはDeploymentでもいいかもしれません(Sentinel は順序性の意味がないので)。
構築(共通)
まずは共有するパスワードをSecretリソースに準備します。 作成方法は単独サーバーのときと同じなのでほぼ再掲になります。 一応、今回は作成リソースが多くなるのであとから検索しやすくするようラベルを追加しておきます。
Secret
-
パスワードファイルを作成
OpenSSL を利用してランダムなパスワード文字列を作成します。 デフォルト指定する base64 オプションだと 約3割り増し の文字列生成されるので、改行削除して先頭512文字をパスワードとして利用します。
openssl rand -base64 512 | tr -d '\r\n' | cut -c 1-512 > redis-password
この後で動作確認をする際に毎回このパスワードを入力します。 パスワードが長いとテスト向けに動作確認するのはつらくなるので、本番まで間はいったん適当にしておくのもあり。
-
KubernetesにSecreetリソースを作成
kubectl create secret generic redis-secret --from-file=redis-password
-
Secreetリソースをラベルを追加
kubectl label secrets/redis-secret app=redis
構築(Replication)
Service
Redis Server 用の Service を作成していきます。
-
マニュフェストの作成
redis-server-service.yml
apiVersion: v1 kind: Service metadata: name: cache-service labels: app: redis spec: ports: - port: 6379 targetPort: 6379 clusterIP: None # Headless Service selector: app: redis # match "StatefulSet spec.template.metadata.labels" type: server
selector
にはapp
以外にもtype
を指定しています。 今回は Redis Server と Sentinel の2つがあるので、その違いをtype
で区別するようにしています。 -
リソースの作成
kubectl apply -f redis-server-service.yml
StatefulSet
Redisサーバーの実態を作成します。
-
マニュフェストの作成
redis-server-statefulset.yml
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis namespace: default labels: app: redis ver: "0.0.1" env: staging spec: selector: matchLabels: app: redis type: server serviceName: cache-service # match "Service metadata.name" replicas: 2 template: metadata: name: redis namespace: default labels: app: redis type: server ver: "0.0.1" env: staging spec: terminationGracePeriodSeconds: 10 containers: - name: redis image: redis:5.0.5-alpine3.10 ports: - containerPort: 6379 command: - "/bin/sh" - "-c" args: - | CONFIG_FILE_PATH=/data/redis.conf create_config() { if [ $HOSTNAME = ${REDIS_STATEFULSETNAME}-0 ]; then # # Master server config file # cat <<-EOF > "${CONFIG_FILE_PATH}" ########### NETWORK ########## bind 0.0.0.0 port 6379 protected-mode no ########### GENERAL ########## daemonize no logfile "" ######## SNAPSHOTTING ######## save 900 1 save 300 10 save 60 10000 dir "/data" dbfilename "dump.rdb" ######### REPLICATION ######## masterauth ${REDIS_PASSWORD} replica-announce-ip "${HOSTNAME}.${REDIS_SERVICENAME}" replica-announce-port 6379 ########## SECURITY ########## requirepass ${REDIS_PASSWORD} ##### MEMORY MANAGEMENT ###### maxmemory 100mb maxmemory-policy allkeys-lru EOF else # # Slave server config file # cat <<-EOF > "${CONFIG_FILE_PATH}" ########### NETWORK ########## bind 0.0.0.0 port 6379 protected-mode no ########### GENERAL ########## daemonize no logfile "" ######## SNAPSHOTTING ######## save 900 1 save 300 10 save 60 10000 dir "/data" dbfilename "dump.rdb" ######### REPLICATION ######## replicaof ${REDIS_STATEFULSETNAME}-0.${REDIS_SERVICENAME} 6379 masterauth ${REDIS_PASSWORD} replica-announce-ip "${HOSTNAME}.${REDIS_SERVICENAME}" replica-announce-port 6379 ########## SECURITY ########## requirepass ${REDIS_PASSWORD} ##### MEMORY MANAGEMENT ###### maxmemory 100mb maxmemory-policy allkeys-lru EOF fi } if [ ! -e ${CONFIG_FILE_PATH} ]; then create_config fi redis-server "${CONFIG_FILE_PATH}" env: - name: "REDIS_STATEFULSETNAME" value: "redis" - name: "REDIS_SERVICENAME" value: "cache-service" - name: "REDIS_PASSWORD" valueFrom: secretKeyRef: name: redis-secret key: redis-password
あまり特筆事項はありませんが…もし設定ファイルを ConfigMap から与えたいのであれば、initContainerやShellで別途コピーして書き込みできるようにする必要があります。
-
リソースの作成
kubectl apply -f redis-server-statefulset.yml
構築(Sentinel)
Redisレプリケーションを監視するセンチネルを構築します。 センチネルはステートフルである必要ありませんが・・・サーバー本体と同じ作りのほうがわかりやすいかと思うので、今回は StatefulSet で準備します。
Service
サーバーを個別に特定できるようHeadlessサービスを作っておきます。
-
マニュフェストの作成
redis-sentinel-service.yml
apiVersion: v1 kind: Service metadata: name: cache-monitoring labels: app: redis spec: ports: - port: 26379 targetPort: 26379 clusterIP: None # Headless Service selector: app: redis # match "StatefulSet spec.template.metadata.labels" type: sentinel
-
リソースの作成
kubectl apply -f redis-sentinel-service.yml
StatefulSet
センチネルを構築します。
-
マニュフェストの作成
redis-sentinel-statefulset.yml
apiVersion: apps/v1 kind: StatefulSet metadata: name: sentinel namespace: default labels: app: redis ver: "0.0.1" env: staging spec: selector: matchLabels: app: redis type: sentinel serviceName: cache-monitoring # match "Service metadata.name" replicas: 3 template: metadata: name: redis namespace: default labels: app: redis type: sentinel ver: "0.0.1" env: staging spec: terminationGracePeriodSeconds: 10 containers: - name: sentinel image: redis:5.0.5-alpine3.10 ports: - containerPort: 26379 command: - "/bin/sh" - "-c" args: - | CONFIG_FILE_PATH=/etc/redis.conf while ! ping -c 1 ${REDIS_MASTER_HOST} &> /dev/null do echo "Waiting for redis master server boot ..." sleep 1 done cat <<-EOF > "${CONFIG_FILE_PATH}" ########### NETWORK ########## bind 0.0.0.0 port 26379 protected-mode no ########### GENERAL ########## daemonize no logfile "" sentinel announce-ip "${HOSTNAME}.${REDIS_SENTINEL_SERVICENAME}" sentinel announce-port 26379 ########## SECURITY ########## requirepass ${REDIS_PASSWORD} ########## SENTINEL ########## sentinel monitor mymaster ${REDIS_MASTER_HOST} 6379 2 sentinel auth-pass mymaster ${REDIS_PASSWORD} sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1 EOF redis-server "${CONFIG_FILE_PATH}" --sentinel env: - name: "REDIS_MASTER_HOST" value: "redis-0.cache-service" - name: "REDIS_SENTINEL_SERVICENAME" value: "cache-monitoring" - name: "REDIS_PASSWORD" valueFrom: secretKeyRef: name: redis-secret key: redis-password
センチネルは3台以上が推奨なので、 replication は 3以上 を指定します。
-
リソースの作成
kubectl apply -f redis-sentinel-statefulset.yml
確認
ここまでに作成したクラスタの動作確認をしていきます。
作成したリソースの確認
-
ラベル指定で作成リソースを確認
[root@k8s-master ~]# kubectl get sts,svc,po,secrets -l app=redis NAME READY AGE statefulset.apps/redis 2/2 13h statefulset.apps/sentinel 3/3 12h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/cache-monitoring ClusterIP None <none> 26379/TCP 12h service/cache-service ClusterIP None <none> 6379/TCP 13h NAME READY STATUS RESTARTS AGE pod/redis-0 1/1 Running 0 146m pod/redis-1 1/1 Running 0 13h pod/sentinel-0 1/1 Running 0 12h pod/sentinel-1 1/1 Running 0 12h pod/sentinel-2 1/1 Running 0 12h NAME TYPE DATA AGE secret/redis-secret Opaque 1 13h
マスター→スレーブへデータがコピーされる動作確認
-
動作確認用Pod作成
kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
-
マスターへログイン
redis-cli -h redis-0.cache-service -a password
-
マスターにデータ投入
set key1 "Hello World"
-
マスターからログアウト
exit
でログアウトします。 -
スレーブにログイン
redis-cli -h redis-1.cache-service -a password
-
スレーブでデータ確認
get key1 "Hello World"
マスター停止時にスレーブがマスターに昇格する動作確認
-
動作確認用Pod作成
kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
-
現在の状態を確認
redis-cli -h redis-0.cache-service -a password info | grep role redis-cli -h redis-1.cache-service -a password info | grep role
-
マスターを停止
redis-cli -h redis-0.cache-service -a password debug sleep 30
-
スレーブが昇格していることを確認
redis-cli -h redis-0.cache-service -a password info | grep role redis-cli -h redis-1.cache-service -a password info | grep role
…と、まぁ、"1回" だけは自動でフェールオーバーしてくれるのですが…2回目を続けて実施すると動かないんですよね。。
おそらく Redis Sentinel が内部的に管理しているものが「IPアドレス」で「ホスト名」ではないことに起因しているように見えます。
また、 sleep
ではなく kubectl delete pod/redis-0
のようなことをすると、そもそもIPアドレスが変わってしまって動きがおかしくなります。
これに対する対策は…「ホスト名解決できる実装を待つ」か「IPアドレスがおかしな場合に補正を行うスクリプトを書く」かどちらかになりそうです。 後者(補正するスクリプト)については実際に作っている人がいる(hortonworks/k8s-redis-ha )ので参考にしてみるとよいかも。
今回は「Kubernetes 上 に Redis Sentinel 構成 を構築する方法」についてまとめました。 ポイントは以下の通りです。
- レプリケーションは StatefulSet で構築、 Headless サービスで特定できるようにする
- センチネルは StatefulSet ないし Deployment で構築
- 標準機能だけだと1回しかフェールオーバーできない
参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!