はじめに
このところ、Kubernetes(k8s)でRedisに触っています。サービスの一部としてRedisを使うとき、ノード障害やミドルウェアのアップデートで、落ちてしまわないかが気になります。Redis自体には冗長化方式はあるのですが、k8sで動かす場合、Operatorの選択肢があります。
この記事ではRedisのOperatorについて調べたことをまとめます。
Redisの冗長化方式
まずRedis自体にどのような冗長化方式があるかです。Redis OperatorはRedisの方式をベースとしているため、ここで整理しておきます。
Redisはマスターとレプリカの構成をとることができ、データを複製できます(Replication)。しかし、Replicationはフェイルオーバーの機能を持たず、マスターがダウンしてもレプリカがマスターに昇格しません。
Redis Sentinelはマスターとレプリカに加え、監視役のSentinelがいます。Sentinelがマスターの状態を監視し、マスターがダウンすればフェイルオーバーを実行します。
Redis Clusterもフェイルオーバーします。また、マルチマスター構成を取り、データを複数のマスターに分散するシャーディングを行っています。しかし、複数のDBを持てずSELECTコマンドが使えません。
方式 | 構成 | フェイルオーバー | 論理DB | シャーディング |
---|---|---|---|---|
Replication | マスター、レプリカ | × | 〇 | × |
Sentinel | マスター、レプリカ、Sentinel | 〇 | 〇 | × |
Cluster | マルチマスター、レプリカ | 〇 | × | 〇 |
他にも外部ツールと組み合わせる方法もあるようです。
※参考
Redis,Sentinelの機能
この記事ではRedis Sentinelについて記載します。
先にRedis,Sentinelの機能について触れ、後述でその機能がOperatorでどのように変わるのかを記載したいと思います。
Redis Sentinelのフェイルオーバーの流れ
Redis Sentinelのフェイルオーバーの流れを図で記載します。
Sentinelがマスターのダウンを検知し、レプリカをマスターに昇格させます。
ダウンの検知は各Sentinelで行われます。SentinelからRedisに対して2秒ごとにhelloメッセージを送り、状態を確認します。この時、マスターのダウンを検知すると主観ダウン状態(SDOWN)にあると判断します。
その後、他のSentinelの判断を確認し、マスターが客観的ダウン状態(ODOWN)にあると判断します。ODOWNであるかどうかは、SDOWNと判断したSentinelの台数が、Sentinelの設定中のQUORUMの値以上であるかで決定されます。
ODOWNとなった後は、フェイルオーバーが実行されます。
フェイルオーバーでは、各Sentinelが次のRedisマスター候補に投票します。投票後、1台のSentinelがリーダーとなり、投票結果を取りまとめ、QUORUMの値以上の投票を集めたRedisを次のマスターとします。その後、リーダーとなったSentinelがRedis Sentinel構成を更新し、レプリカをマスターに昇格させます。もし投票数がQUORUMだけ集めたRedisが存在しない場合、フェイルオーバーは失敗し、一定時間後、再度試行されます。
※参考
Redisのデータ永続化
Redisのデータをディスク上に保存し、ダウン後の再起動でデータを復元する方法について記載します。
データを保存する方法にはRDBとAOF(Append Only File)があります。
RDBではデータをファイルに圧縮して保存します。指定時間に変更されたデータ数に対する保存頻度を設定できます。
AOFではデータとコマンドを圧縮せずにファイルに保存します。以下に簡単な例を示します。
root@redis-pod:/data# redis-cli set test 1
root@redis-pod:/data# cat appendonlydir/appendonly.aof.1.incr.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
test
$1
1
それぞれの比較について記載します。
項目 | 比較 | メモ |
---|---|---|
必要なディスクサイズ | RDB<AOF | AOFは圧縮せずコマンドとデータを保存するため。 |
Redisダウン時のデータロスの大きさ | RDB>AOF | AOFは追記分だけ保存する。そのため保存頻度を大きくしてロスを小さくしやすい。 |
ファイル読み込み速度 | RDB>AOF | AOFではデータに加えコマンドを読み込んでいるため。 |
永続化失敗の可能性 | RDB<AOF | RDBはアトミックに保存が可能。AOFはバグで復元が失敗したことがあるらしい。 |
ファイル復元のしやすさ | RDB<AOF | AOFでは破損を修正するツールがある。また直接ファイルを修正しやすいため。 |
どちらもメリット、デメリットがあり、使う場面に応じて、片方もしくは両方を採用するとよいそうです。
※参考
Redisのセキュリティ
Redisではアクセス制限を設定できます。bindの設定では、アクセスできるクライアントのIPアドレスを制限できます。
また、ユーザーを作成し、認証と権限を設定できます。以下、setとgetコマンドの権限を持つユーザー「myaccount」を作成する例です。
root@redis-pod:/data# redis-cli
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
127.0.0.1:6379> acl setuser myaccount on ~* &* >mypassword -@all +get +set
OK
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user myaccount on #89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8 ~* &* -@all +set +get"
127.0.0.1:6379> exit
root@redis-pod:/data# redis-cli --user myaccount --pass mypassword
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> set test aaa
OK
127.0.0.1:6379> get test
"aaa"
127.0.0.1:6379> config get dir
(error) NOPERM this user has no permissions to run the 'config|get' command
※参考
Redis Operatorの機能
今回はSentinelを使った以下のgithubのOperatorについて記載します。
k8sの機能を使えるようになっており、便利になっています。
Redis Sentinelの構成、設定の監視
Operatorは以下のことを確認し、問題があれば自動で修正してくれます。
・起動しているRedisの台数が設定通りである。
・起動しているSentinelの台数が設定通りである。
・マスターは1台のみ起動している。
・レプリカが同じマスターに接続されている。
・Sentinelが同じマスターを監視している。
・SentinelはダウンしたRedis、Sentinelを監視していない。
Operatorなしとの違いとしては、Redisの台数を確認してくれることが挙げられると思います。Operatorなしでは、マスターダウン時のフェイルオーバー後、何もしないとレプリカが存在しない構成となってしまいます。Operatorを使うと、新しくレプリカとしてRedisのPODが立ち上がり、マスターレプリカ構成が保たれます。
また、Redis、Sentinelの接続先を監視しているため、コマンドミスなどで接続先が変わった際は修正してくれます。
※参考
カスタムリソースの一部として設定できる
Operatorでは、データ永続化のためのPV(PersistentVolume)をカスタムリソースの一部として定義できます。
PVを使うことによりRedis外部にデータ永続化のためのファイルを保存できます。OperatorなしではPVの定義はカスタムリソースと別々でしなければならないため、Operatorによりリソースの管理が楽になると思います。
RedisのOperatorを使ってみる
実際にk8s環境を作ってOperatorを使ってみます。
k8s環境を作成する
まずはk8s環境を作ります。基本はこれまでと同じですが、パッケージのバージョンを指定しないと困った経験があるので、指定します。
#!/bin/bash -ex
echo 'install docker'
sudo yum install -y docker-20.10.13-2.amzn2.x86_64
sudo systemctl start docker
sudo systemctl enable docker
echo 'install k8s'
echo "`ip -4 a show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'` `hostname`" | sudo tee -a /etc/hosts
echo "
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
" | sudo tee -a /etc/yum.repos.d/kubernetes.repo > /dev/null
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
sudo yum install -y kubelet-1.24.0-0.x86_64 kubeadm-1.24.0-0.x86_64 kubectl-1.24.0-0.x86_64 --disableexcludes=kubernetes
sudo swapoff -a
sudo systemctl start kubelet
sudo systemctl enable kubelet
echo '
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
' | sudo tee /etc/docker/daemon.json > /dev/null
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart kubelet
sudo kubeadm init
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
echo 'setup control-plane node'
K8S_VERSION=$(kubectl version | base64 | tr -d '\n')
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$K8S_VERSION"
CONTROL_PLANE=`kubectl get node | grep 'control-plane' | cut -d' ' -f1`
kubectl taint nodes $CONTROL_PLANE node-role.kubernetes.io/master:NoSchedule-
kubectl taint nodes $CONTROL_PLANE node-role.kubernetes.io/control-plane:NoSchedule-
それますが、マスター、スレーブの呼び方は変わる流れとなっているようで、コマンドも少し変わっています。
Redis Operatorをインストールする
Helmを使ってRedis Operatorをインストールします。
#!/bin/bash -ex
SCRIPT_DIR=$(cd $(dirname $0); pwd)
echo 'install helm'
HELM_VERSION='v3.8.1'
cd $HOME
curl "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" -o helm.tar.gz
tar -zxvf helm.tar.gz
mv linux-amd64 helm
echo 'export PATH=$PATH:$HOME/helm' >> $HOME/.bashrc
source .bashrc
helm version
echo 'install redis-operator'
REDIS_OPERATOR_VERSION='v1.1.1'
REDIS_NAMESPACE='redis'
cd $SCRIPT_DIR
helm repo add redis-operator https://spotahome.github.io/redis-operator
helm repo update
helm install redis-operator redis-operator/redis-operator \
--set image.tag=$REDIS_OPERATOR_VERSION \
-n $REDIS_NAMESPACE \
--create-namespace \
-f config/redis-operator-values.yaml
image:
repository: quay.io/spotahome/redis-operator
pullPolicy: Always
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 100m
memory: 128Mi
インストール時にOperatorのメモリ、CPUなどを指定できます。
Redis Sentinelを作成する
サンプルを参考にyamlファイルを作成し、Redis Sentinelを作成します。
また、カスタムリソース中に、PODと同様に各種設定を入れることができます。
apiVersion: databases.spotahome.com/v1
kind: RedisFailover
metadata:
name: redisfailover
namespace: redis
spec:
sentinel:
replicas: 3
resources:
requests:
cpu: 100m
limits:
memory: 100Mi
redis:
replicas: 2
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 400m
memory: 500Mi
storage:
persistentVolumeClaim:
metadata:
name: rf-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1Gi
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
カスタムリソース以外のリソースの定義は省略します。
今回はカスタムリソース中に各種の設定を入れました。
また、Redis Operatorの構成の作成自体はyamlファイルを適用するだけでできます。楽でよいと思います。
kubectl apply -f rf-basic.yaml
以下、カスタムリソース中の設定について記載します。
・データ永続化としてPVを使用している。
/data
配下PVがマウントされ、ファイルが書き込まれます。
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfr-redisfailover-0 -it -- sh
/data $ ls
dump.rdb
/data $ pwd
/data
今回は記事を書く時間の都合上、ローカルストレージを使いましたが、AWSのEBSやEFSを使うことが可能です。
・アフィニティを使用してPODの配置をノード単位で分散できるようにしている。
ただ今回はノードは1つしかないので特に意味はありません。
また、githubのページではSecretを使って認証の設定が行えることが書いてありましたが、エラーで実施できませんでした。
[ec2-user@ip-10-0-0-53 ~]$ kubectl get pod -n redis
NAME READY STATUS RESTARTS AGE
redis-operator-777cdb655b-f2qxq 1/1 Running 0 143m
rfr-redisfailover-0 0/1 Running 0 35m
rfr-redisfailover-1 0/1 Running 0 35m
rfs-redisfailover-55b5fd485b-g9kz9 1/1 Running 0 35m
rfs-redisfailover-55b5fd485b-ldlvn 1/1 Running 0 35m
rfs-redisfailover-55b5fd485b-qhsct 1/1 Running 0 35m
[ec2-user@ip-10-0-0-53 ~]$ kubectl logs -n redis redis-operator-777cdb655b-f2qxq | tail -n 3
time="2022-05-21T14:09:35Z" level=info msg="podDisruptionBudget updated" namespace=redis podDisruptionBudget=rfs-redisfailover service=k8s.podDisruptionBudget src="poddisruptionbudget.go:79"
time="2022-05-21T14:09:35Z" level=info msg="deployment updated" deployment=rfs-redisfailover namespace=redis service=k8s.deployment src="deployment.go:102"
time="2022-05-21T14:09:35Z" level=error msg="error on object processing: WRONGPASS invalid username-password pair or user is disabled." controller-id=redisfailover object-key=redis/redisfailover operator=redisfailover service=kooper.controller src="controller.go:279"
Redis Operatorにアクセスする
作成したRedis Sentinelにクライアントからアクセスしてみます。redis-cliコマンドが使えるPODを作成してやってみます。
[ec2-user@ip-10-0-0-53 ~]$ kubectl apply -f redis.yaml
pod/redis-pod created
[ec2-user@ip-10-0-0-53 ~]$ kubectl get svc -n redis
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis-operator ClusterIP 10.105.178.232 <none> 9710/TCP 164m
rfs-redisfailover ClusterIP 10.100.210.109 <none> 26379/TCP 13m
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis redis-pod -it -- bash
root@redis-pod:/data# redis-cli -h rfs-redisfailover -p 26379 sentinel get-master-addr-by-name mymaster
1) "10.32.0.8"
2) "6379"
root@redis-pod:/data# redis-cli -h 10.32.0.8 set testdata 100
OK
root@redis-pod:/data# redis-cli -h rfs-redisfailover -p 26379 sentinel replicas mymaster
1) 1) "name"
2) "10.32.0.9:6379"
3) "ip"
4) "10.32.0.9"
5) "port"
6) "6379"
7) "runid"
8) "5dd7d0cb8c3aabea01c5ec95cab15ec89a992db8"
9) "flags"
10) "slave"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "109"
19) "last-ping-reply"
20) "109"
21) "down-after-milliseconds"
22) "5000"
23) "info-refresh"
24) "3605"
25) "role-reported"
26) "slave"
27) "role-reported-time"
28) "826627"
29) "master-link-down-time"
30) "0"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "10.32.0.8"
35) "master-port"
36) "6379"
37) "slave-priority"
38) "100"
39) "slave-repl-offset"
40) "381554"
41) "replica-announced"
42) "1"
root@redis-pod:/data# redis-cli -h 10.32.0.9 get testdata
"100"
SentinelのServiceからマスターのアドレスを確認することで、Redisへデータの読み書きができました。
フェイルオーバー時の挙動を確認する
次にRedisマスターがダウンした際のフェイルオーバーの挙動を見てみます。ダウンの状況としてRedisマスターのPODを削除します。
[ec2-user@ip-10-0-0-53 ~]$ kubectl get pod -n redis -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-operator-777cdb655b-f2qxq 1/1 Running 0 170m 10.32.0.4 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
redis-pod 1/1 Running 0 12m 10.32.0.10 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-0 1/1 Running 0 19m 10.32.0.8 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-1 1/1 Running 0 19m 10.32.0.9 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-2967z 1/1 Running 0 19m 10.32.0.5 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-6pw27 1/1 Running 0 19m 10.32.0.6 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-fds5w 1/1 Running 0 19m 10.32.0.7 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-2967z -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.8:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-53 ~]$ kubectl delete pod -n redis rfr-redisfailover-0
pod "rfr-redisfailover-0" deleted
マスターがダウンすると、フェイルオーバーが行われます。フェイルオーバーによりマスターが変わったことを確認します。
[ec2-user@ip-10-0-0-53 ~]$ kubectl get pod -n redis -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-operator-777cdb655b-f2qxq 1/1 Running 0 173m 10.32.0.4 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
redis-pod 1/1 Running 0 15m 10.32.0.10 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-0 1/1 Running 0 93s 10.32.0.8 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-1 1/1 Running 0 23m 10.32.0.9 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-2967z 1/1 Running 0 23m 10.32.0.5 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-6pw27 1/1 Running 0 23m 10.32.0.6 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-fds5w 1/1 Running 0 23m 10.32.0.7 ip-10-0-0-53.ap-northeast-1.compute.internal <none> <none>
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-2967z -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.9:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-2967z -- redis-cli -p 26379 sentinel replicas mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
name
10.32.0.8:6379
ip
10.32.0.8
port
6379
runid
4ae072844f52afdffff3f166a0f4dfbdf603c40c
flags
slave
link-pending-commands
0
link-refcount
1
last-ping-sent
0
last-ok-ping-reply
568
last-ping-reply
568
down-after-milliseconds
5000
info-refresh
2217
role-reported
slave
role-reported-time
192467
master-link-down-time
0
master-link-status
ok
master-host
10.32.0.9
master-port
6379
slave-priority
100
slave-repl-offset
485233
replica-announced
1
結果から、Redisマスターがrfr-redisfailover-0(10.32.0.8)からrfr-redisfailover-1(10.32.0.9)に変わっていることが確認できました。rfr-redisfailover-0はスレーブとなっていました。
Redis Operatorでは、PODが削除されてもIPアドレスが変わらないようにされているようです。RedisとSentinelの両方でIPアドレスが変わりません。これでRedisにとってIPアドレスが変わって都合の悪い諸々が解決されているように思います。
誤った設定となったときの挙動を確認する
OperatorはRedisとSentinelの設定面も監視しているとのことなので、確認してみます。
今回はSentinelのマスターの監視対象を削除してみます。
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-2967z -- redis-cli -p 26379 sentinel remove mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
OK
[ec2-user@ip-10-0-0-53 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-2967z -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.9:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-53 ~]$ kubectl logs -n redis rfs-redisfailover-55b5fd485b-2967z
・・・
1:X 21 May 2022 14:45:59.027 # -monitor master mymaster 10.32.0.9 6379
1:X 21 May 2022 14:46:06.318 # +monitor master mymaster 10.32.0.9 6379 quorum 2
・・・
結果から、設定変更後、約7秒後、再設定されていました。
これはRedis Sentinelにはない機能なので、ありがたいと思います。
スプリットブレイン時の挙動を確認する
最後にスプリットブレインが起こった際の挙動について記載します。
Redis Sentinelでのスプリットブレインでは、各Sentinelが別々のRedisをマスターだと認識していることになります。簡単に図で例を示します。
上図ではSentinel間とSentinelとRedis間の接続障害が同時に発生した場合を考えています。
この時、Sentinel1にとって、Sentinel2,3とRedis2(レプリカ)がダウンしているように見えます。
Sentinel2,3にとっては、Sentinel1とRedis1(マスター)がダウンしているように見えます。
Sentinel2,3はマスターがダウンしているように見えるため、フェイルオーバーを実行してRedis2をマスターに昇格させます。しかし、Sentinel1はそのことを知らないため、Redis1がマスターだと思い続けています。結果、Sentinel1とSentinel2,3で異なるRedisのマスターと接続していることになります。もし、この時クライアントから接続されると、データロスの可能性があります。
Redis Operatorではスプリットブレインが起きた時、どうなるか確認します。
準備としてchaos-meshをインストールします。
#!/bin/bash -ex
SCRIPT_DIR=$(cd $(dirname $0); pwd)
echo 'install chaos-mesh'
CHAOS_MESH_VERSION='2.1.3'
INSTALL_NAMESPACE='chaos-testing'
cd $SCRIPT_DIR
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm install chaos-mesh chaos-mesh/chaos-mesh \
--version $CHAOS_MESH_VERSION \
-n $INSTALL_NAMESPACE \
--create-namespace \
-f config/chaos-mesh-values.yaml
echo 'annotate redis node'
CONTROL_PLANE=`kubectl get node | grep 'control-plane' | cut -d' ' -f1`
kubectl annotate node $CONTROL_PLANE chaos-mesh.org/inject=enabled
controllerManager:
enableFilterNamespace: 'true'
chaosDaemon:
runtime: 'containerd'
socketPath: '/var/run/containerd/containerd.sock'
Redis Operatorに接続の障害を起こします。
以下のyamlファイルを作成します。POD名を指定して接続を切断するものです。
apiVersion: chaos-mesh.org/v1alpha1
kind: Workflow
metadata:
name: rf-chaos
namespace: chaos-testing
spec:
entry: rf-networkchaos
templates:
- name: rf-networkchaos
templateType: Parallel
children:
- redis-master
- redis-slave
- sentinel
- name: redis-master
templateType: NetworkChaos
networkChaos:
action: partition
mode: all
selector:
pods:
redis:
- rfr-redisfailover-0
direction: to
target:
mode: all
selector:
pods:
redis:
- rfs-redisfailover-55b5fd485b-jks7t
- rfs-redisfailover-55b5fd485b-pp6qg
- name: redis-slave
templateType: NetworkChaos
networkChaos:
action: partition
mode: all
selector:
pods:
redis:
- rfr-redisfailover-1
direction: to
target:
mode: all
selector:
pods:
redis:
- rfs-redisfailover-55b5fd485b-4cltl
- name: sentinel
templateType: NetworkChaos
networkChaos:
action: partition
mode: all
selector:
pods:
redis:
- rfs-redisfailover-55b5fd485b-4cltl
direction: to
target:
mode: all
selector:
pods:
redis:
- rfs-redisfailover-55b5fd485b-jks7t
- rfs-redisfailover-55b5fd485b-pp6qg
上記yamlファイルを適用します。
[ec2-user@ip-10-0-0-199 ~]$ kubectl apply -f rf-netchaos.yaml
workflow.chaos-mesh.org/rf-chaos created
Redis Operatorの状態を確認します。
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-4cltl -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.13:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-jks7t -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.14:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-pp6qg -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.14:6379,slaves=1,sentinels=3
SentinelからRedisマスターのIPアドレスを確認してみると、1台だけIPアドレスが異なり、スプリットブレインが起きていることがわかります。
また、PODの状態を見てみます。
[ec2-user@ip-10-0-0-199 ~]$ kubectl get pod -n redis -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-operator-777cdb655b-2w546 1/1 Running 0 24m 10.32.0.4 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-0 1/1 Running 0 4m24s 10.32.0.13 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
rfr-redisfailover-1 1/1 Running 0 4m24s 10.32.0.14 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-4cltl 1/1 Running 0 4m23s 10.32.0.11 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-jks7t 1/1 Running 0 4m23s 10.32.0.10 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
rfs-redisfailover-55b5fd485b-pp6qg 1/1 Running 0 4m23s 10.32.0.12 ip-10-0-0-199.ap-northeast-1.compute.internal <none> <none>
READYの欄が1/1となっており、クライアントからアクセス可能であることがわかります。データロスが問題となる場合、何かしら対策が必要だと思います。
Operatorのログを見てみます。
[ec2-user@ip-10-0-0-199 ~]$ kubectl logs -n redis redis-operator-777cdb655b-2w546 | tail
time="2022-05-22T02:26:21Z" level=info msg="configMap updated" configMap=rfr-redisfailover namespace=redis service=k8s.configMap src="configmap.go:78"
W0522 02:26:21.493347 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
W0522 02:26:21.497182 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
time="2022-05-22T02:26:21Z" level=info msg="podDisruptionBudget updated" namespace=redis podDisruptionBudget=rfr-redisfailover service=k8s.podDisruptionBudget src="poddisruptionbudget.go:79"
time="2022-05-22T02:26:21Z" level=info msg="statefulSet updated" namespace=redis service=k8s.statefulSet src="statefulset.go:102" statefulSet=rfr-redisfailover
W0522 02:26:21.507657 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
W0522 02:26:21.511551 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
time="2022-05-22T02:26:21Z" level=info msg="podDisruptionBudget updated" namespace=redis podDisruptionBudget=rfs-redisfailover service=k8s.podDisruptionBudget src="poddisruptionbudget.go:79"
time="2022-05-22T02:26:21Z" level=info msg="deployment updated" deployment=rfs-redisfailover namespace=redis service=k8s.deployment src="deployment.go:102"
time="2022-05-22T02:26:21Z" level=error msg="error on object processing: More than one master, fix manually" controller-id=redisfailover object-key=redis/redisfailover operator=redisfailover service=kooper.controller src="controller.go:279"
ログの最後の1文で、マスターが複数いるため手動での修正が必要といったエラー文が出ています。
最後に接続障害を取り除いてみます。
[ec2-user@ip-10-0-0-199 ~]$ kubectl delete -f rf-netchaos.yaml
workflow.chaos-mesh.org "rf-chaos" deleted
[ec2-user@ip-10-0-0-199 ~]$ kubectl logs -n redis redis-operator-777cdb655b-2w546 | tail -n 20
・・・
time="2022-05-22T02:28:51Z" level=error msg="error on object processing: More than one master, fix manually" controller-id=redisfailover object-key=redis/redisfailover operator=redisfailover service=kooper.controller src="controller.go:279"
time="2022-05-22T02:29:21Z" level=info msg="configMap updated" configMap=rfs-redisfailover namespace=redis service=k8s.configMap src="configmap.go:78"
time="2022-05-22T02:29:21Z" level=info msg="configMap updated" configMap=rfr-s-redisfailover namespace=redis service=k8s.configMap src="configmap.go:78"
time="2022-05-22T02:29:21Z" level=info msg="configMap updated" configMap=rfr-readiness-redisfailover namespace=redis service=k8s.configMap src="configmap.go:78"
time="2022-05-22T02:29:21Z" level=info msg="configMap updated" configMap=rfr-redisfailover namespace=redis service=k8s.configMap src="configmap.go:78"
W0522 02:29:21.474784 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
W0522 02:29:21.478397 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
time="2022-05-22T02:29:21Z" level=info msg="podDisruptionBudget updated" namespace=redis podDisruptionBudget=rfr-redisfailover service=k8s.podDisruptionBudget src="poddisruptionbudget.go:79"
time="2022-05-22T02:29:21Z" level=info msg="statefulSet updated" namespace=redis service=k8s.statefulSet src="statefulset.go:102" statefulSet=rfr-redisfailover
W0522 02:29:21.487929 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
W0522 02:29:21.491568 1 warnings.go:70] policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
time="2022-05-22T02:29:21Z" level=info msg="podDisruptionBudget updated" namespace=redis podDisruptionBudget=rfs-redisfailover service=k8s.podDisruptionBudget src="poddisruptionbudget.go:79"
time="2022-05-22T02:29:21Z" level=info msg="deployment updated" deployment=rfs-redisfailover namespace=redis service=k8s.deployment src="deployment.go:102"
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-4cltl -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.14:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-jks7t -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.14:6379,slaves=1,sentinels=3
[ec2-user@ip-10-0-0-199 ~]$ kubectl exec -n redis rfs-redisfailover-55b5fd485b-pp6qg -- redis-cli -p 26379 info | grep mymaster
Defaulted container "sentinel" out of: sentinel, sentinel-config-copy (init)
master0:name=mymaster,status=ok,address=10.32.0.14:6379,slaves=1,sentinels=3
少し経って構成が更新されました。全Sentinelの接続先のRedisマスターがフェイルオーバー後のものになっていました。接続障害がなくなれば自動で復旧を試みるようです。
以上でスプリットブレインの確認を終わります。
おわりに
今回、Redis Operatorを使ってみて、実際にOperatorを採用するかどうかは状況次第だなと思いました。
ただ、Redisを使って実現したいことがOperatorでもできるなら、便利ではないかと思います。
まだまだインフラで知らないことが多いです。これからも勉強します。