お世話になっております。
しゃまとんです。

前にredis-clusterを試してみた記事を作成したのですが、今回はGKEでクラスタを作ってみることにしました。 というのもWebDB PRESSで特集されていてやってみようと思ったのがきっかけです。

という記事なんですが、結局、ボツに至ったものになります。 作業ログとして残しておきたいと思います。本自体はとても勉強になりました。

やったこととしては独自のredisコンテナ作成し、起動時に自分の名前に沿ってmaster/slaveを判断して、 clusterに参加するといったものです。それではまずredisイメージファイル(Dockerfile)になります。

FROM redis:3.2.4-alpine

MAINTAINER shamaton

RUN apk add --update bind-tools curl && \
    rm -rf /var/cache/apk/*

COPY bootstrap.sh /bootstrap.sh
COPY redis.conf /conf/redis.conf

CMD ["redis-server", "/conf/redis.conf"]

公式にRedisコンテナに起動スクリプトと設定ファイル(conf)を追加しています。
起動スクリプト(bootstrap.sh)と設定ファイル(redis.conf)は下記のような感じです。 スクリプトにはchmodで実行権限をつけておいてください(chmod +x)

#!/bin/sh
# set -e

SEQ_START=0
SEQ_END=16383

PET_NUMBER=$(cat /etc/podinfo/pod_name | cut -d- -f3)
PET_ORDINAL=$(expr ${PET_NUMBER} % ${REPLICA_SIZE})

MASTER_NUMBER=$(expr ${PET_NUMBER} - ${PET_ORDINAL})

# master server setting
if [ ${PET_ORDINAL} = "0" ]; then
  GROUP_NUM=$(expr ${REPLICA_NUM} / ${REPLICA_SIZE})
  GROUP_END_INDEX=$(expr ${GROUP_NUM} - 1)

  SEQ_PER=$(expr ${SEQ_END} / ${GROUP_NUM})

  GROUP_INDEX=$(expr ${PET_NUMBER} / ${REPLICA_SIZE})

  SET_SEQ_START=$(expr ${SEQ_PER} \* ${GROUP_INDEX} + 1)
  SET_SEQ_END=$(expr ${SET_SEQ_START} + ${SEQ_PER} - 1)

  if [ ${GROUP_INDEX} = "0" ]; then
    SET_SEQ_START=0
  fi

  if [ ${GROUP_INDEX} = "${GROUP_END_INDEX}" ]; then
    SET_SEQ_END=${SEQ_END}
  fi

  echo "cluster set seq : ${SET_SEQ_START} ${SET_SEQ_END}"

  redis-cli cluster addslots $(seq ${SET_SEQ_START} ${SET_SEQ_END})
fi

# meet redis-cluster-0
if [ ! ${PET_NUMBER} = "0" ]; then

  MASTER_ZERO_FULLNAME=redis-cluster-0.redis-cluster.default.svc.cluster.local
  MASTER_ZERO_IP=$(host ${MASTER_ZERO_FULLNAME} | cut -d ' ' -f 4)

  echo "MASTER_ZERO_IP : ${MASTER_ZERO_IP}"

  redis-cli cluster meet ${MASTER_ZERO_IP} 6379
fi

sleep 1

# slave server setting
if [ ! ${PET_ORDINAL} = "0" ]; then

  MASTER_FULLNAME=redis-cluster-${MASTER_NUMBER}.redis-cluster.default.svc.cluster.local
  MASTER_IP=$(host ${MASTER_FULLNAME} | cut -d ' ' -f 4)

  echo "MASTER_IP : ${MASTER_IP}"

  MASTER_ID=$(redis-cli cluster nodes | grep ${MASTER_IP} | cut -d ' ' -f 1)

  echo "MASTER_ID : ${MASTER_ID}"

  redis-cli cluster replicate ${MASTER_ID}
fi

wait
appendonly yes
cluster-enabled yes
cluster-require-full-coverage no
cluster-node-timeout 5000
cluster-config-file nodes.conf
cluster-require-full-coverage yes
cluster-migration-barrier 1
protected-mode no

用意できたら、イメージを作成してContainer Registryに追加します。

docker build -t redis-cluster:${version} .
docker tag redis-cluster:${version} asia.gcr.io/your_project_id/redis-cluster:${version}
gcloud docker -- push asia.gcr.io/your_project_id/redis-cluster:${version}

それではクラスタを作成して、redisクラスタをKubernetes上で作成してみましょう。
配置にひつようなyamlファイル(cluster.yaml)は下記のようになります。(プロジェクトIDは置き換えてください)

#
# Redis Cluster service
#
apiVersion: v1
kind: Service
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
spec:
  ports:
  - port: 6379
    name: redis
  clusterIP: None
  selector:
    app: redis-cluster
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: redis-cluster
spec:
  serviceName: redis-cluster
  replicas: 6
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      containers:
      - name: redis-cluster
        image: asia.gcr.io/your_project_id/redis-cluster:${version}
        imagePullPolicy: Always
        lifecycle:
          postStart:
            exec:
              command: [ "/bootstrap.sh" ]
        ports:
          - containerPort: 6379
            name: client
          - containerPort: 16379
            name: gossip
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "redis-cli -h $(hostname) ping"
          initialDelaySeconds: 15
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "redis-cli -h $(hostname) ping"
          initialDelaySeconds: 20
          periodSeconds: 3
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: REPLICA_NUM
            value: "6"
          - name: REPLICA_SIZE
            value: "2"
        volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
          readOnly: false
      volumes:
      - name: podinfo
        downwardAPI:
          items:
            - path: "labels"
              fieldRef:
                fieldPath: metadata.labels
            - path: "annotations"
              fieldRef:
                fieldPath: metadata.annotations
            - path: "pod_name"
              fieldRef:
                fieldPath: metadata.name
            - path: "pod_namespace"
              fieldRef:
                fieldPath: metadata.namespace

これを適用させます。すると各redisのpodが起動スクリプトで自分の役割を把握し必要に応じたredisコマンドを実行していきます。

gcloud container clusters create test-cluster --machine-type=f1-micro
kubectl apply -f cluster.yaml
#ログ確認コマンド

# pod一覧
kubectl get pods

# ログ確認
kubectl logs ${pod_name}

# service確認
kubectl get service

試しに接続確認してみましょう。podを1つ用意して接続してみます。
Serviceから接続できるはずです。

kubectl run -i --tty ubuntu --image=ubuntu --restart=Never /bin/bash
apt-get update
apt-get install ruby vim wget redis-tools

# 接続
redis-cli -c -h redis-cluster
redis-cluster:6379> set hoge fuga
-> Redirected to slot [1525] located at 10.48.0.7:6379
OK

10.48.0.7:6379>; get hoge
"fuga"</pre>

このようにクラスタが自動で生成されて、接続確認することができました。
ただこの方法だとpodが死んでしまった場合に、redis-cluster側が再配置するのと、 kubernetes側のpod再生成で辻褄が合わなくなるのではと思いました。何か対処が必要になる感じだったので、 redis-trib.rbを使ったクラスタ生成に変えることにしました。

ファイル自体の定義もちょっとイケてないものだし、特性を活かすには物足りないものだったかなと思います。 それでもredis-clusterがどのように構築されるかわかったので良かったと思います。

以上です。

あ、後片付けが必要な方はお忘れなく!

kubectl delete -f cluster.yaml
gcloud container clusters delete test-cluster

■参考