shibatch's journey

日々考えていることをつらつら書くだけです

KubernetesでRedisを構築してわかったこと/検証試験をしてわかったこと

KubernetesでRedisを構築した。

構成決定まで紆余曲折があったのだけど、それは置いておくとして、最終的には以下の素朴な構成に落ち着いた。

Redis構成

StatefulSetで4Replicasで構成する。それぞれのRedisはStandaloneで動くので、メモリ情報はPod間で共有しない。今回の要件では特に共有は不要だったのでこのようなシンプルな構成にした。

それぞれのPodがPersistentVolumeClaim(PVC)をマウントする。ApplicationはRedisへはService Resourceを介してアクセスする。なお、メモリの容量は1GiBにした。

StatefulSetを構築してわかったこと

StatefulSetの構築は初めてだったのだけれど、ひとつ新しく知ったのはStatefulSetごとに別のPVCをマウントする場合、StatefulSetの中にvolumeClaimTemplatesというパラメータを設定すると1対1でマウントするPVCを自動で作ってくれる。しかもPVCのResourceを別途作成する必要もない。こんな感じ。

  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
      storageClassName: volume-storage-class

初めて知ったのだけれどめちゃくちゃ便利だ。 ref: StatefulSet | Kubernetes

これで以下のような構成になる。

% k get statefulset -n redis
NAME    READY   AGE
redis   4/4     2d18h
% k get pod -n redis
NAME      READY   STATUS    RESTARTS   AGE
redis-0   1/1     Running   0          19h
redis-1   1/1     Running   0          19h
redis-2   1/1     Running   0          19h
redis-3   1/1     Running   0          19h
% k get pvc -n redis
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
data-redis-0   Bound    pvc-8a2080c6-edb5-4c25-8366-7ea8d4f958f2   1Gi        RWO            volume-storage-class   2d18h
data-redis-1   Bound    pvc-5af5bad2-2b84-49bc-9b63-9368aaea9648   1Gi        RWO            volume-storage-class   2d18h
data-redis-2   Bound    pvc-17ad4657-bd7d-4f4a-a7d9-c2da90a01423   1Gi        RWO            volume-storage-class   2d18h
data-redis-3   Bound    pvc-7b797afe-570f-4e19-9ebc-da30372aedee   1Gi        RWO            volume-storage-class   2d18h

Redisのパラメータ調整

StatefulSetでRedisが起動した後、設定をながめているとデフォルト値ではまずいな、と思ったことがひとつあった。

redis-svc.redis.svc.cluster.local:6379> INFO Memory
<snip>
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
<snip>

これはつまりメモリの格納量が飽和するとそれ以上格納できなくなることを示している。ref: Key eviction | Redis

なのでメモリのサイズを使い切ったら使用頻度の低いキーを削除するポリシーに変えた。

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
  namespace: redis
data:
  redis.conf: |
    maxmemory 1gb
    maxmemory-policy allkeys-lfu

メモリ限界検証の試験をした

  • StatefulSetのリソース設定でメモリのLimitを1Giにした
  • Redisの設定で使えるメモリも1GBとした
  • 使われないキーから削除する

これらの設定でちゃんと動作するのかが不安だったのでメモリ限界検証の試験をした。

RedisのPodに向けて、キーを格納しまくる。(DEBUGコマンドがあるようだがkeyをsetするほうがしっくりくるのでシェルで回した)

%  k exec -it redis-pod -- /bin/bash
root@redis-pod:/data# for i in {1..1000000}; do redis-cli -h 10.233.134.136 set test$i "$(for l in {1..10000}; do echo 1234567890;done)" > /dev/null; done

これを実施してしばらくしたら、Redisのメモリが1024Mで頭打ちになった。

root@redis-pod:/data# redis-cli -h 10.233.134.136 info memory | grep used_memory_human
used_memory_human:1023.81M
root@redis-pod:/data# redis-cli -h 10.233.134.136 info memory | grep used_memory_human
used_memory_human:1023.83M
root@redis-pod:/data# redis-cli -h 10.233.134.136 info memory | grep used_memory_human
used_memory_human:1023.86M

キーの数は減っていくような動作に見えた

root@redis-pod:/data# redis-cli -h 10.233.134.136 info Keyspace
# Keyspace
db0:keys=123803,expires=0,avg_ttl=0
root@redis-pod:/data# redis-cli -h 10.233.134.136 info Keyspace
# Keyspace
db0:keys=123541,expires=0,avg_ttl=0
root@redis-pod:/data# redis-cli -h 10.233.134.136 info Keyspace
# Keyspace
db0:keys=123280,expires=0,avg_ttl=0

evicted_keysはガンガン増えていったので、消されていっていることがわかる

root@redis-pod:/data# redis-cli -h 10.233.134.136 info stats | grep evicted_keys
evicted_keys:83580

Podをtopでみるとこちらも1024Miで頭打ちになっていた。

2023年 4月21日 金曜日 15時55分40秒 JST
NAME      CPU(cores)   MEMORY(bytes)
redis-2   57m          1023Mi
2023年 4月21日 金曜日 15時55分45秒 JST
NAME      CPU(cores)   MEMORY(bytes)
redis-2   57m          1023Mi

この状態で新規にキーバリューを格納したら正常に動作することを確認できた。

root@redis-pod:/data# redis-cli -h 10.233.134.136 set check_testdata1 "check_testdata_put1"
OK
root@redis-pod:/data# redis-cli -h 10.233.134.136 get check_testdata1
"check_testdata_put1"

検証してわかったこと

Redisはオンメモリデータベースなので、Podのメモリ使用量もRedisで設定するmaxmemoryの値は使うことを意識しないといけない。 つまり、

  • RedisでStatefulSetを構築するときはメモリのリソース設定が必須。1Gi使いたい場合はrequest / limitともに1Gi以上の値を入れる必要がある。
    • 自分の場合はRedisのmaxmemory、StatefulSetのRequest、StatefulSetのLimitをすべて1Giに合わせた。これでちゃんと動いてそうだったので。
  • maxmemory-policy はちゃんと設定しましょう

ということがわかった。

なお、CPUリソースの設定値がまだよくわからないのでLimitだけ多めに設定して、あとはVPAを入れて運用しながら調整することにした。

参考までに、自分がつくったyamlファイルを置いておく。

redis yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
  namespace: redis
data:
  redis.conf: |
    maxmemory 1gb
    maxmemory-policy allkeys-lfu
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: redis
spec:
  serviceName: redis
  replicas: 4
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      nodeSelector:
        storage: redis-storage
      tolerations:
      - key: "type"
        operator: "Equal"
        value: "redis-storage"
        effect: "NoExecute"
      containers:
      - name: redis
        image: redis:7.0.11
        command: ["redis-server", "/redis-standalone/redis.conf"]
        resources:
          requests:
            memory: "1Gi"
          limits:
            memory: "1Gi"
            cpu: "1"
        env:
        - name: ALLOW_EMPTY_PASSWORD
          value: "yes"
        volumeMounts:
        - name: data
          mountPath: /data
        - name: config
          mountPath: /redis-standalone
      volumes:
      - name: config
        configMap:
          name: redis-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
      storageClassName: volume-storage-class
---
apiVersion: v1
kind: Service
metadata:
  name: redis-svc
  namespace: redis
spec:
  ports:
  - port: 6379
    targetPort: 6379
    name: redis
  clusterIP: None
  selector:
    app: redis
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: redis
  namespace: redis
spec:
  targetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: redis
  updatePolicy:
    updateMode: "Off"