Kubernetes 上に Redis Sentinel 構成 を構築する方法

0 件のコメント

今回は「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リソースに準備します。 作成方法は単独サーバーのときと同じなのでほぼ再掲になります。 一応、今回は作成リソースが多くなるのであとから検索しやすくするようラベルを追加しておきます。

  1. パスワードファイルを作成

    OpenSSL を利用してランダムなパスワード文字列を作成します。 デフォルト指定する base64 オプションだと 約3割り増し の文字列生成されるので、改行削除して先頭512文字をパスワードとして利用します。

    openssl rand -base64 512 | tr -d '\r\n' | cut -c 1-512 > redis-password
    

    この後で動作確認をする際に毎回このパスワードを入力します。 パスワードが長いとテスト向けに動作確認するのはつらくなるので、本番まで間はいったん適当にしておくのもあり。

  2. KubernetesにSecreetリソースを作成

    kubectl create secret generic redis-secret --from-file=redis-password
    
  3. Secreetリソースをラベルを追加

    kubectl label secrets/redis-secret app=redis
    

構築(Replication)

Redis Server 用の Service を作成していきます。

  1. マニュフェストの作成

    redis-server-service.yml

    apiVersion: 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 で区別するようにしています。

  2. リソースの作成

    kubectl apply -f redis-server-service.yml
    

Redisサーバーの実態を作成します。

  1. マニュフェストの作成

    redis-server-statefulset.yml

    apiVersion: 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で別途コピーして書き込みできるようにする必要があります。

  2. リソースの作成

    kubectl apply -f redis-server-statefulset.yml
    

構築(Sentinel)

Redisレプリケーションを監視するセンチネルを構築します。 センチネルはステートフルである必要ありませんが・・・サーバー本体と同じ作りのほうがわかりやすいかと思うので、今回は StatefulSet で準備します。

サーバーを個別に特定できるようHeadlessサービスを作っておきます。

  1. マニュフェストの作成

    redis-sentinel-service.yml

    apiVersion: 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
    
  2. リソースの作成

    kubectl apply -f redis-sentinel-service.yml
    

センチネルを構築します。

  1. マニュフェストの作成

    redis-sentinel-statefulset.yml

    apiVersion: 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以上 を指定します。

  2. リソースの作成

    kubectl apply -f redis-sentinel-statefulset.yml
    

確認

ここまでに作成したクラスタの動作確認をしていきます。

  1. ラベル指定で作成リソースを確認

    [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
    

  1. 動作確認用Pod作成

    kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
    
  2. マスターへログイン

    redis-cli -h redis-0.cache-service -a password
    
  3. マスターにデータ投入

    set key1 "Hello World"
    
  4. マスターからログアウト

    exit でログアウトします。

  5. スレーブにログイン

    redis-cli -h redis-1.cache-service -a password
    
  6. スレーブでデータ確認

    get key1
    "Hello World"
    

  1. 動作確認用Pod作成

    kubectl run -it redis-debug --image=redis:5.0.5-alpine3.10 --restart=Never --rm -- sh
    
  2. 現在の状態を確認

    redis-cli -h redis-0.cache-service -a password info | grep role
    redis-cli -h redis-1.cache-service -a password info | grep role
    
  3. マスターを停止

    redis-cli -h redis-0.cache-service -a password debug sleep 30
    
  4. スレーブが昇格していることを確認

    redis-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 の フォロー」 お願いします!!