Kubernetes2 Advent Calendar 2020 3日目: redis-operatorの導入
この記事は Kubernetes2 Advent Calendar 2020 の 3 日目です。
直前に空きが出ていたので光速で書きました。 枠が勿体無いですからね。がんばりました。
2日目がCRDだったのでOperatorの話にしました。
直前でアレなのですが書く時間を確保できなさそうなので12/3の枠を解放しました🙏
— ポジティブな Tori (@toricls) 2020年12月2日
どなたか代わりにぜひ〜
/ "Kubernetes2 Advent Calendar 2020 - Qiita" https://t.co/izWa5v7Ysa
これはなに?
フルマネージドサービスを使わずにHA構成のRedisをk8s上で動かして運用したい人向け。
HA構成なのでシャーディング使いたい人向けではないです。
redis-operatorは色々ありますがこちらを使います。
master/slave + sentinelsのRedis HAが爆誕します。
この記事の動機
金食い虫のRedisのマネージドサービスからの脱却
みんなで導入して運用の知見を高め隊
導入の流れ
Namespaceはお好きに作成しておいて以下の流れです。
- redis-operatorをapply
- CRをapply
- おしまい!
主な特徴
- master/slave + sentinelsのRedis HAが爆誕
- Redisエンドポイントも爆誕
- Read Replicaのスケールが容易
- クラスタ内アクセス想定
- Sentinelなのでシャーディングは使えない
Operatorの復習
Operator知らずにとりあえずインストールしたらどうしたらいいかわからなくなったので、
概念理解に参考になりそうな記事を置いておきます。
OperatorはCRDとCustomControllerを実装したもので、それにCRを食わせると定義したものを爆誕させる奴と理解しました。
Sentinelの復習
そもそもRedisには高可用性の設定以下があって今回はSentinelの話。
- Replication: マスターとレプリカのレプリケーション
- Sentinel: 死活監視と自動フェイルオーバーを行ってくれて、ReplicationをHAにしてくれる。
- Cluster: マルチマスター構成。データを複数サーバに分散するシャーディングでwriteがscaleする奴。
Sentinelはquorum(多数決)を取るので3台以上必要になります。
多数決でフェイルオーバーを行うかどうかを決めます。
ノードのDOWNにも2つあって、自身が検知したSDOWN (Subjectively Down),quorumの数に達したODOWN (Objectively Down)があります。
Operatorのインストール
公式でHelm Chartが用意されているので、それを使うだけでインストールできます。 サービスではhelmfileで入れています。
デフォルトのvalues.yamlではRBACが作られないので rbac.install = true
で専用のRBACを作るようにします。Namespaceも区切りました。
Redisのインストール
RedisFailoverリソースを定義します。
redisはStatefulSet, sentinelsはDeploymentとしてPodが起動します。
rfr-
が redis
で rfs-
は sentinel
になります。
kgp -n redis NAME READY STATUS RESTARTS AGE rfr-redisfailover-0 1/1 Running 0 141m rfr-redisfailover-1 1/1 Running 0 145m rfr-redisfailover-2 1/1 Running 0 21h rfs-redisfailover-7d6869df6-7k5gt 1/1 Running 0 137m rfs-redisfailover-7d6869df6-82785 1/1 Running 0 3h9m rfs-redisfailover-7d6869df6-wlxz7 1/1 Running 0 139m
EndpointもClusterIPができあがります。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE rfs-redisfailover ClusterIP 10.36.2.254 <none> 26379/TCP 46h
対マイクロサービス
マイクロサービスでKeyが衝突しないようにデータベースを分けて使います。
設定例
RedisFailoverというCRDを使ってリソースを作成します。
公式で設定例集があり色んなパターンがあるので参考になります。
またcustom-configでredisの設定を書くことも可能です。
- imageのupdate
updateStrategyはOnDelete typeでした。更新をapplyしたあとにPodが削除される振る舞いでrollingupdateとは違った振る舞いです。
- 高可用性の設定
Sentinelとredisはそれぞれ同じノードに乗らないようにすることで可用性が高まります。
podAntiAffinityを指定して相乗りしないようにするのがおすすめです。
podAntiAffinityの設定の書き方の場合はこれとか参考になります。
app.kubernetes.io/nameやapp.kubernetes.io/componentはredis-operationで管理されているLabelなのでこれを活用するとよいです。
- failoverの設定
down-after-milliseconds: Master/SlaveのDOWNN検知後、SDOWNに移行するまでの時間で、デフォルトが5秒です。
failover-timeout: failoverのtimeoutです。(ms)
- 実行ユーザの変更
root実行ではなく実行ユーザの情報が変更されていることを確認
$ k exec -it rfs-redisfailover-7d6869df6-4nfxl -n redis -- id uid=1000 gid=1000 groups=1000
全体の設定のsample
sentinel: replicas: 3 image: redis:6.0.9 resources: requests: cpu: 100m memory: 100Mi limits: cpu: 100m memory: 100Mi customConfig: - "down-after-milliseconds 2000" - "failover-timeout 3000" securityContext: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app.kubernetes.io/name: redisfailover app.kubernetes.io/component: sentinel topologyKey: kubernetes.io/hostname # redis(master/slave) pods redis: replicas: 3 image: redis:6.0.9 resources: requests: cpu: 100m memory: 200Mi limits: cpu: 100m memory: 200Mi securityContext: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app.kubernetes.io/name: redisfailover app.kubernetes.io/component: redis topologyKey: kubernetes.io/hostname
監視
Datadogでとれるredisのメトリクス一覧はこちら。
以下のメトリクスでreplicationとEvictionとメモリ使用量の監視をしました。
他に監視したほうがいいものがあれば
- redis.replication.last_io_seconds_ago
- redis.keys.evicted
- redis.mem.used
障害対応
基本はPod数かmemoryのresourceを増やす
動作確認
まずRedisのMasterでSet/Getできるか確認
$ kubectl exec -ti rfs-redisfailover-7d6869df6-h7fwr -n redis -- redis-cli -h 10.36.2.254 -p 26379 10.36.2.254:26379> SENTINEL get-master-addr-by-name mymaster 1) "10.32.4.8" 2) "6379" $ kubectl exec -ti rfs-redisfailover-7d6869df6-h7fwr -n redis -- redis-cli -h 10.32.4.8 10.32.4.8:6379> set hello world OK 10.32.4.8:6379> get hello "world"
次にフェイルオーバーも確認していきます。
このコマンドで30秒間スリープしてmasterにaccessできないようにします。
masterが何かの理由でhung状態になる状況を想定した動作検証です。
$ kubectl exec -ti rfs-redisfailover-7d6869df6-h7fwr -n redis -- redis-cli -h 10.36.2.254 -p 26379
SENTINEL get-master-addr-by-name <master name>
はmasterのipとport番号を名前と一緒に返します。masterが 10.32.4.8
から 10.32.13.4
になったのがわかります。
$ kubectl exec -ti rfs-redisfailover-7d6869df6-h7fwr -n redis -- redis-cli -h 10.36.2.254 -p 26379 10.36.2.254:26379> SENTINEL get-master-addr-by-name mymaster 1) "10.32.4.8" 2) "6379" 10.36.2.254:26379> SENTINEL get-master-addr-by-name mymaster 1) "10.32.13.4" 2) "6379"
ここでsentinelのログからフェイルオーバーの動作を見ていきます。
ログから以下のアクションがわかります。
- 各Sentinelはマスタが
+sdown
イベントでダウンしたことを検知します。 - このイベントは後で
+odown
に昇格されます。それは複数のSentinelがマスタが到達不可能である事実に同意したことを意味します。 - Sentinel は最初フェイルオーバーの試行を開始するSentinelに投票します。
- フェイルオーバーが発生します。
sentinelのログ
rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:10.831 # +set master mymaster 10.32.4.8 6379 failover-timeout 3000 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:25.503 # +new-epoch 10 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:25.508 # +vote-for-leader 5051f1baca46e38724a816c720439f2960ff00e8 10 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:25.623 # +sdown master mymaster 10.32.4.8 6379 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:25.678 # +odown master mymaster 10.32.4.8 6379 #quorum 3/2 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:25.678 # Next failover delay: I will not start a failover before Wed Nov 25 06 :45:32 2020 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:26.631 # +config-update-from sentinel 5051f1baca46e38724a816c720439f2960ff00e8 10.32.1.9 26379 @ mymaster 10.32.4.8 6379 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:26.631 # +switch-master mymaster 10.32.4.8 6379 10.32.13.4 6379 rfs-redisfailover-7d6869df6-4nfxl sentinel 1:X 25 Nov 2020 06:45:26.631 * +slave slave 10.32.7.11:6379 10.32.7.11 6379 @ mymaster 10.32.13.4 63 79
先程書き込めた 10.32.4.8
がリードレプリカとなって書き込めないのも確認できます。
10.32.4.8:6379> set hey you (error) READONLY You can't write against a read only replica.
プリエンプティブ(スポット)インスタンスを使用している場合
いつノードが落ちるかわからないのでプリエンプティブ(スポット)インスタンス上では起動させたくない場合はNodeAffinityを導入するとよいです。GKEの場合は、requiredDuringSchedulingIgnoredDuringExecutionで cloud.google.com/gke-preeptible
ラベルが表示されるノードにはPodがスケジュールされないようにします。
- requiredDuringSchedulingIgnoredDuringExecution
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: cloud.google.com/gke-preemptible operator: DoesNotExist
また、preferredDuringSchedulingIgnoredDuringExecutionで cloud.google.com/gke-preemptible
ラベルが表示されるノードに可能ならスケジュール、そうでなければ他にスケジュールするといった設定も可能です。
- preferredDuringSchedulingIgnoredDuringExecution
affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - preference: matchExpressions: - key: cloud.google.com/gke-preemptible operator: Exists weight: 100
以上がredis-operatorの導入になります。 その他にこうした方がいいなどありましたらブログのコメントにください。
明日の Kubernetes2 Advent Calendar 2020 4日目は @iaoiui さんの「KubeCon NA参加してみたのでKubernetesの今後を予測してみる」です!お楽しみに!