今回は「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文字をパスワードとして利用します。
1openssl rand -base64 512 | tr -d '\r\n' | cut -c 1-512 > redis-password
この後で動作確認をする際に毎回このパスワードを入力します。 パスワードが長いとテスト向けに動作確認するのはつらくなるので、本番まで間はいったん適当にしておくのもあり。
-
KubernetesにSecreetリソースを作成
1kubectl create secret generic redis-secret --from-file=redis-password
-
Secreetリソースをラベルを追加
1kubectl label secrets/redis-secret app=redis
構築(Replication)
Service
Redis Server 用の Service を作成していきます。
-
マニュフェストの作成
redis-server-service.yml
1234567891011121314apiVersion: 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
で区別するようにしています。 -
リソースの作成
1kubectl apply -f redis-server-service.yml
StatefulSet
Redisサーバーの実態を作成します。
-
マニュフェストの作成
redis-server-statefulset.yml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129apiVersion: 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で別途コピーして書き込みできるようにする必要があります。
-
リソースの作成
1kubectl apply -f redis-server-statefulset.yml
構築(Sentinel)
Redisレプリケーションを監視するセンチネルを構築します。 センチネルはステートフルである必要ありませんが・・・サーバー本体と同じ作りのほうがわかりやすいかと思うので、今回は StatefulSet で準備します。
Service
サーバーを個別に特定できるようHeadlessサービスを作っておきます。
-
マニュフェストの作成
redis-sentinel-service.yml
1234567891011121314apiVersion: 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
-
リソースの作成
1kubectl apply -f redis-sentinel-service.yml
StatefulSet
センチネルを構築します。
-
マニュフェストの作成
redis-sentinel-statefulset.yml
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879apiVersion: 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以上 を指定します。
-
リソースの作成
1kubectl apply -f redis-sentinel-statefulset.yml
確認
ここまでに作成したクラスタの動作確認をしていきます。
作成したリソースの確認
-
ラベル指定で作成リソースを確認
123456789101112131415161718[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作成
1kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
-
マスターへログイン
1redis-cli -h redis-0.cache-service -a password
-
マスターにデータ投入
1set key1 "Hello World"
-
マスターからログアウト
exit
でログアウトします。 -
スレーブにログイン
1redis-cli -h redis-1.cache-service -a password
-
スレーブでデータ確認
12get key1
"Hello World"
マスター停止時にスレーブがマスターに昇格する動作確認
-
動作確認用Pod作成
1kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
-
現在の状態を確認
12redis-cli -h redis-0.cache-service -a password info | grep role
redis-cli -h redis-1.cache-service -a password info | grep role
-
マスターを停止
1redis-cli -h redis-0.cache-service -a password debug sleep 30
-
スレーブが昇格していることを確認
12redis-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回しかフェールオーバーできない
参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!