K8SにRedis Clusterの構築記

# はじめに
これは [KWC Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kwc) の記事です。

こんにちは。KWCでSREを担当する、パクヒョンギョンです。
今回、K8Sの勉強のため、個人で開発しているKubernetes Operatorを書いてみよう思います。
この記事は「[https://tech.kakao.com/2022/02/09/k8s-redis/](https://tech.kakao.com/2022/02/09/k8s-redis/)」こちらの記事の内容通り実際に開発してみたことなどをまとめたものです。
韓国語の記事ですが、PM（Physical Machine）に乗っているRedisをVM（Virtual Machine）に変更してK8S環境に運用する手順と問題点が記載してあります。

# この記事を作成しようとした切っ掛け
K8Sは実務で使ったことがなく独学していますので、関連知識を高めるために今回の記事を作成しました。
記事の内容と間違っているところや記事を読みながらよく分からない部分は勝手に解析して開発したものもありますので
あくまでこの記事は参考用でみて頂けると幸いです。

# 構成方法
結論からいうとKubernetes Operator Patternを使いました。そして、
HAのためPod Affinityを適用し、モニタリングのためにRedis ExporterとPrometheus、そしてGrafanaを使いました。
韓国の記事にはRedisの性能のためHost Networkを使っていますが、
```
Containers:
  Port: 80/TCP
  Host Port: 80/TCP
```
こちらの記事ではRedisクラスタにアクセスするにはIngress Controllerを使って通信するように開発してみようと思います。
* [Kubernetes Operator Pattern](https://sdk.operatorframework.io/)
* Ingress Controller
* Pod Affinity
* Redis Exporter + Prometheus + Grafana

# CRDの動きと詳細
## Redis Controller (Reconcile)
Kubernetesは定義されたアプリケーションの意図した状態（Desired State）と現在の状態（Current State）が一致しているかを監視して一致してない場合、これをControllerから調整（Reconcile）する作業が実施され、意図した状態になります。
例として以下は「Replication Controller」の動きとしては
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985788/4861401e-3356-8604-5065-87c3ec1fc1e7.png)
になります。
今回は（KRedis）というCustom Resourceを作りますので「Redis Controller」を開発しようとします。
ちなみにDeploymentとかJobとか既存K8SリソースにもControllerがあります。
[https://developers.redhat.com/articles/2021/06/22/kubernetes-operators-101-part-2-how-operators-work#deploying_workloads_in_kubernetes](https://developers.redhat.com/articles/2021/06/22/kubernetes-operators-101-part-2-how-operators-work#deploying_workloads_in_kubernetes)
![image.png](https://developers.redhat.com/sites/default/files/styles/article_floated/public/operator-reconciliation-kube-only.png?itok=ZLMeudHd)

### CRDが追加された場合
まだ、開発中ですが、CRを作るとRedis ControllerからReconcile関数が実行され、Deploymentが立ち上がるのとPodのSpecなどをお見せします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985788/5d287b80-5571-0d6f-771c-ec4371017b13.png)
まず、`make deploy`でcontrollerをClusterにディプロイできます。詳しい内容はOperatorをインストールして「Makefile」の中身をご覧ください。
```
% kubectl get all -n operator-system
NAME                                               READY   STATUS    RESTARTS   AGE
pod/operator-controller-manager-567b446c69-jrrdx   2/2     Running   0          18s

NAME                                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/operator-controller-manager-metrics-service   ClusterIP   10.***.***.***   <none>        8443/TCP   19s

NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/operator-controller-manager   1/1     1            1           19s

NAME                                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/operator-controller-manager-567b446c69   1         1         1       19s
```
`get`で確認してみると私が作ったcontrollerが「deployment.apps/operator-controller-manager」名のdeploymentで立ち上がっているのを確認できました。

基本的にリソースが生成されるネームスペースはプロジェクト名になります。
そして、Controllerも「Replicas 1、Surge 100%」Deploymentリソースになります。

controllerが上がっている状態でCRを生成してみます。
```
% kubectl apply -f config/samples/stable_v1_kredis.yaml -n operator-system
kredis.stable.example.com/kredis-sample created
% kubectl get kredis -n operator-system
NAME            AGE
kredis-sample   60s
```
問題なく生成されて`get`で確認しても「kredis-sample」名で立ち上がっているのを確認できました。
私が追加したSpec通り設定されているかも確認してみます。
```
% kubectl describe kredis kredis-sample -n operator-system | grep -A9 "Spec"
Spec:
  Base Port:  10000
  Image:      {AWS ECR}/redis-cluster:latest
  Masters:    2
  Memory:     1gb
  Replicas:   1
  Resource:
    Requests:
      Cpu:     2
      Memory:  2000Mi
```
Specには問題なさそうですのでReconcileによってdeploymentまで生成されたのか確認してみます。
```
% kubectl get all -n operator-system | grep -B1 "kredis"
NAME                                               READY   STATUS    RESTARTS   AGE
pod/kredis-sample-7bc8b59fbc-vptdb                 1/1     Running   0          7m32s
--
NAME                                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kredis-sample                                 ClusterIP   10.***.***.***   <none>        6379/TCP   7m33s
--
NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kredis-sample                 1/1     1            1           7m33s
--
NAME                                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/kredis-sample-7bc8b59fbc                 1         1         1       7m33s
```
deploymentも問題なく立ち上がっていて「pod/kredis-sample-7bc8b59fbc-vptdb」Podに入ってRedisコンソールに入れるのも確認できました。
一応、今回はここまで開発ができましたが、残りは今後時間があれば続けて開発しようと思います。

ちなみにOperatorがK8Sリソースを操作するには、Service Accountに権限（ClusterRole）を追加する必要があります。
[https://book.kubebuilder.io/reference/markers/rbac.html](https://book.kubebuilder.io/reference/markers/rbac.html)

### CRDが更新された場合：（HPA）
ClusterにCRDが既にある場合、更新（Reconcile）作業を行います。こちらの処理もCustom Controllerで直接、開発する必要があります。←こちらはCRD追加処理の開発が終わってないので更新処理はまだ、未実装です。
そして、普通に`kind: HorizontalPodAutoscaler`のリソースを使ってHPAを実装しようとしましたが、通常のHPAはDeploymentのReplicasを調整してくれますので
今回、調整したかったのは

* Memory 負荷の場合 => Master Nodeを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985788/e90aa513-bef9-cbb4-7b76-8d2e7c083eae.png)
* CPU 負荷の場合    => Replica Nodeを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985788/c161afcc-cd5e-185e-87eb-d2afd09e1567.png)

を実装したかったのですが、CRDのSpecを調整する方法がわからないため現在継続調査中です。
[https://book.kubebuilder.io/reference/generating-crd.html#scale](https://book.kubebuilder.io/reference/generating-crd.html#scale)
こちらをみるとCRDにもHPAのためにOperatorにコードを追加して設定するのがあるようですので今後、参考して開発するつもりです。

### Redis Cluster HA処理：(failover)
Redis Cluster自体でMasterノードが落ちるとreplica設定で結んでいるslave nodeが自動でMasterノード（failover）になります。

## Redisポッドの構成
### topology key
HAのためにMaster nodeとReplica nodeのPodを別のワーカーノードに配置するため、labelを追加して対応しようとしますが、
```
requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector: 
        matchLabels:
          nodeId: "masterNodeId"
      topologykey: "kubernetes.io/hostname"
```
こちらもまだ、開発中です。一応、上の仕様通り設定すればいけるかなと思っていて前の開発が終わってから検証してみようと思います。

### CRD Spec
以下はCRDのSpecになります。
```
spec:
  masters: 2 # Redis Cluster のMaster Node 数
  replicas: 1 # Redis Cluster のReplica Node 数
  memory: 1gb # これはRedisのMax Memory
  basePort: 10000 # redis port: 10000, redis exporter port: 15000, Cluster bus port: 20000, 
  image: 
  resource:
    requests:
      cpu: '2'
      memory: 2000Mi
```
[Cluster bus port](https://redis.io/docs/management/scaling/)

OperatorでCRDのSpecを変えるには「{crd}_types.go」を修正する必要があります。
[https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#define-the-api](https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#define-the-api)
```
// KRedisSpec defines the desired state of KRedis
type KRedisSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	// Foo is an example field of KRedis. Edit kredis_types.go to remove/update
	Image    string                       `json:"image"`
	Masters  int32                        `json:"masters"`
	Replicas int32                        `json:"replicas"`
	Memory   string                       `json:"memory"`
	BasePort int32                        `json:"basePort"`
	Resource map[string]map[string]string `json:"resource"`
}
```

## モニタリング
### metrics-server インストール
モニタリングとHPAを設定するためには metrics-server をインストールします。
```
$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
```
これで`kubectl top ~`のコマンドが実行できるようになりました。CPUとMemory使用量のmetric情報はetcdに保存され、スケジューラがスケールアウトする時やPrometheusが情報収集する際に使用されます。

### Prometheus、Grafana
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985788/976695ef-dcd7-cb27-d578-f627c32ca241.png)

[Prometheus](https://github.com/prometheus/prometheus/blob/main/documentation/examples/prometheus-kubernetes.yml)
こちらを参考にConfigMapを作ってPrometheusを構築し、Grafanaまで設定しました。
後、Redis Exporterを入れてPrometheusのTargetを設定をする必要がありますが、調査と構築するには時間がかかりそうですので今回は監視インフラだけ設定しました。

## まだ、未実装のところ
* 監視（Redis Exporter）の設定
* CRDの更新やスケールイン・アウト（HPA）の時、追加されたPodをRedis Clusterに追加、削除
* Master nodeとReplica nodeのRedis Podを別のワーカーノードに配置されるようにAffinity設定（HA）
* Podが落ちて削除された場合、Replica nodeを追加　←　（削除されたら他のPodがMasterに昇格されるので）
* （Liveness, Readiness）でPodの状態を確認
* Ingress Controllerと紐づいて疎通テスト
* Roll Back処理
* 検証

# おわりに
RedisをK8S上に運用することでメリットとしてはHA構成であってスケールイン・アウトもしやすくなった点ではないかと思います。
今回、KubernetesのCRDとCustom Controllerを直接、開発してみてちょっとK8Sに慣れてきましたので
もし、K8Sを勉強しようとする方にはOperatorを直接、開発してみることをおすすめします。
ここまで読んで頂いてありがとうございます。記事の作成と並行で開発してて未実装のところが多く、ほとんど、日記のような内容でしたが、皆様にお役に立てれば幸いです。

[KWC Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kwc) はまだまだ続きます！明日の投稿もお楽しみに！