레이블이 Redis인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Redis인 게시물을 표시합니다. 모든 게시물 표시

redis sentinel 구성 및 테스트

redis cluster 구성
로컬 장비에서 포트를 달리해 redis 노드들을 띄운다.

--cluster-replicas n 명시하지 않으면 마스터 노드로만 구성한다.
1 이면 master 당 slave 1개로 구성한다.
클러스터는 최소 3개의 마스터가 필요, 슬레이브까지 구성하려면 6개의 노드가 필요하다.
./redis/bin/redis-cli --cluster create --cluster-replicas 1 -a "ysoftmanPassword123" \
127.0.0.1:7001 \
127.0.0.1:7002 \
127.0.0.1:7003 \
127.0.0.1:7004 \
127.0.0.1:7005 \
127.0.0.1:7006

위와 같이 3개의 마스터 3개의 슬레이브로 구성된 클러스터를 확인
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c cluster nodes


#####


sentinel 구성
이제 sentinel 을 구성하면 모니터링, 알림, failover(slave->master변경)이 가능하다.
sentinel.conf 주요 설정

port 27001
protected-mode no
daemonize yes
pidfile "sentinel27001.pid"
logfile "sentinel27001.log"
# 마스터 노드 모두를 모니터링 한다.
# 3개의 sentinel 중 2개이상(quorum(정족수),과반수) 마스터 내려갔음을 인지해야 동작(slave->master승격)하도록 한다.
sentinel monitor redis1 127.0.0.1 7001 2
sentinel monitor redis2 127.0.0.1 7005 2
sentinel monitor redis3 127.0.0.1 7003 2
# redis master 접속에 암호가 필요한경우
sentinel auth-pass redis1 ysoftmanPassword123
sentinel auth-pass redis2 ysoftmanPassword123
sentinel auth-pass redis3 ysoftmanPassword123
# redis master 가 10 초동안 응답없으면 다운으로 인지
# 참고로 디폴트 30초,디폴트값이면 sentinel 시작시 .conf 재작성할때 이설정은 삭제된다.
sentinel down-after-milliseconds redis1 10000
sentinel down-after-milliseconds redis2 10000
sentinel down-after-milliseconds redis3 10000
# 장애조치(failover) 120초 동안 응답이 없으면 취소한다.
# 참고로 디폴트 180초,디폴트값이면 sentinel 시작시 .conf 재작성할때 이설정은 삭제된다.
sentinel failover-timeout redis1 120000
sentinel failover-timeout redis2 120000
sentinel failover-timeout redis3 120000
# redis 장애 인지시 노티에 사용할 스크립트
# sentinel notification-script redis1 sentinel-notify.sh

현재 마스터 노드의 포트 파악 후
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c cluster nodes | grep master | awk '{print $2}' | sed 's/.*://' | sed 's/@.*//' | tr '\n' ' '

설정에서 마스터 포트를 변경(명시)해서 sentinel 을 실행시킨다.
./redis/bin/redis-sentinel sentinel27001.conf
./redis/bin/redis-sentinel sentinel27002.conf
./redis/bin/redis-sentinel sentinel27003.conf


#####


sentinel 동작 테스트
7001, 7002, 7003 포트가 마스터 노드인 상황
7002 노드에 값을 저장한다.
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c
127.0.0.1:7001> set ysoftman orange
-> Redirected to slot [8041] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get ysoftman
"orange"

7002 노드를 제거해 보자
kill -9 $(ps -ef | grep -E ".*redis.*:7002" | grep -v "grep" | awk '{print $2}')

7002 master 노드 fail 상태, 7005 slave -> master 변경됨
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c cluster nodes
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
34b867023af79468bf4f2926b060eca5741455b1 127.0.0.1:7003@17003 master - 0 1635090090468 3 connected 10923-16383
972b73d32638376b59cb5e6609b217e3d3286f44 127.0.0.1:7002@17002 master,fail - 1635090070264 1635090063193 2 disconnected
6c3507cbc50ee1cae5bba336ff1168f1b92be67d 127.0.0.1:7004@17004 slave 06948db08ba4f8a59dc3b717f1325151ffe2fc8e 0 1635090091476 1 connected
92640be3b64770324a700418acacb7c770fd8b93 127.0.0.1:7005@17005 master - 0 1635090090000 7 connected 5461-10922
b8939e0cf3c49d6b62172fc2cfcd20225fbefd79 127.0.0.1:7006@17006 slave 34b867023af79468bf4f2926b060eca5741455b1 0 1635090092488 3 connected
06948db08ba4f8a59dc3b717f1325151ffe2fc8e 127.0.0.1:7001@17001 myself,master - 0 1635090091000 1 connected 0-5460

3개의 sentinel.log 에 7002 -> 7005 로 마스터 노드를 변경해 failover 처리 되었음을 알 수 있다.
57677:X 25 Oct 2021 00:41:26.895 # +switch-master redis2 127.0.0.1 7002 127.0.0.1 7005
57677:X 25 Oct 2021 00:41:26.896 * +slave slave 127.0.0.1:7002 127.0.0.1 7002 @ redis2 127.0.0.1 7005
57677:X 25 Oct 2021 00:41:36.935 # +sdown slave 127.0.0.1:7002 127.0.0.1 7002 @ redis2 127.0.0.1 7005

7005는 slave 였을때 7002 의 백업을 하고 있었고
이제 master 가 된 7005 에서 7002 에 저장 했던 값을 찾을 수 있다.
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:7001> get ysoftman
-> Redirected to slot [8041] located at 127.0.0.1:7005
"orange"
127.0.0.1:7005>

죽은 7002 노드를 시작하면
./redis/bin/redis-server redis7002.con

7002 는 slave 로 동작한다.
./redis/bin/redis-cli -p 7001 -a "ysoftmanPassword123" -c cluster nodes
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
34b867023af79468bf4f2926b060eca5741455b1 127.0.0.1:7003@17003 master - 0 1635090263000 3 connected 10923-16383
972b73d32638376b59cb5e6609b217e3d3286f44 127.0.0.1:7002@17002 slave 92640be3b64770324a700418acacb7c770fd8b93 0 1635090262960 7 connected
6c3507cbc50ee1cae5bba336ff1168f1b92be67d 127.0.0.1:7004@17004 slave 06948db08ba4f8a59dc3b717f1325151ffe2fc8e 0 1635090262353 1 connected
92640be3b64770324a700418acacb7c770fd8b93 127.0.0.1:7005@17005 master - 0 1635090263364 7 connected 5461-10922
b8939e0cf3c49d6b62172fc2cfcd20225fbefd79 127.0.0.1:7006@17006 slave 34b867023af79468bf4f2926b060eca5741455b1 0 1635090264374 3 connected
06948db08ba4f8a59dc3b717f1325151ffe2fc8e 127.0.0.1:7001@17001 myself,master - 0 1635090263000 1 connected 0-5460

7002 노드의 replication 정보를 보면 7005 를 master 로 바라보고 있음을 알 수 있다.
./redis/bin/redis-cli -p 7002 -a "ysoftmanPassword123" info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7005
... 생략 ...

redis 장애 발생시 sentinel 로 slave->master 로 변경은 해주지만,
sentinel 알림(스크립트)을 통해 장애를 인지하고
redis 노드를 다시 띄우거나 상황에 따라 구성을 변경해야 할 필요는 있다.

# redis sentinel 테스트 내용

redis aof, rdb 백업 비활성화 하기

# k8s redis-cluster 운영 중 liveness, readiness probe 가 가끔 실패한다.
# redis pod 내의 로그를 보면 백업이 수행 기록이 남아 있다.

Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
10 changes in 300 seconds. Saving...
Background saving started by pid 31442
DB saved on disk
RDB: 0 MB of memory used by copy-on-write
Background saving terminated with success


# RDB(현재 메모리의 전체 내용을 dump.rdb 바이너리 파일로 백업, AOF 보다 파일이 사이즈가 작고 빠름)
# AOF(append only file, redis 명령 실행 마다 파일로 백업)
# 백업으로 느려 질 수 있어 백업을 비활성한다.

# k8s pod 에서 redis-cli 접속
kubectl -n redis-cluster exec -it redis-cluster-0 -- redis-cli -h 127.0.0.1 -p 6379 -c -a "mypassword"

# redis RDB 백업 활성화 여부 확인
127.0.0.1:6379> config get save
1) "save"
2) "900 1 300 10 60 10000"

# redis AOF 백업 활성화 여부 확인
# 참고로 config get *  또는 config get aaa* 로 와이드카드도 사용할 수 있다.
127.0.0.1:6379> config get appendonly*
1) "appendonly"
2) "yes"

# redis 노드 하나 재시작 없이 AOF 비활성화
127.0.0.1:6379> config set appendonly no
OK
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"

# redis 노드 하나 재시작 없이 RDB 비활성화
127.0.0.1:6379> config set save ""
OK
127.0.0.1:6379> config get save
1) "save"
2) ""

# 위 방식은 redis 인스턴스(pod)마다 접속해서 수행야 한다.
# 따라서 다음과 같은 스크립트를 실행하는게 편한다.
for i in $(kubectl get pod -n redis-cluster | awk '{print $1}' | grep -v NAME); do
    echo $i;
    # AOF 비활성화
    kubectl -n redis-cluster exec -it $i -- redis-cli -h 127.0.0.1 -p 6379 -c -a "mypassword" config set appendonly no
    # RDB 비활성화
    kubectl -n redis-cluster exec -it $i -- redis-cli -h 127.0.0.1 -p 6379 -c -a "mypassword" config set save ""
    # 확인
    kubectl -n redis-cluster exec -it $i -- redis-cli -h 127.0.0.1 -p 6379 -c -a "mypassword" config get appendonly
    kubectl -n redis-cluster exec -it $i -- redis-cli -h 127.0.0.1 -p 6379 -c -a "mypassword" config get save
done


# 또는 redis.conf 에 다음과 같이 설정해 적용한다.
appendonly no
save ""



#####


# redis-cli 대신 GUI 클라이언트를 사용하면 편하다.

# brew 로 바로 설치할 수있다.
brew install another-redis-desktop-manager


redis 특정 슬롯에 저장하기

# CROSSSLOT Keys in request don't hash to the same slot
# mset, mget 의 키들은 하나의 슬롯(노드)에 있어야 한다.
# 키이름에 {xxx}: 같은 prefix 를 붙이면 xxx 로만 해싱해 슬롯을 지정한다.

# scan 0 으로 전체 조회는 현재 슬롯에서만 가능하기 때문에
# 키에 {xxx} hashing prefix 가 있으면
# 다음과 같이 특정 슬롯으로 이동 후 scan 0 으로 조회해야 한다.

redis-cli -h 10.10.10.101 -p 6379 -c -a "password123"
10.10.10.101:6379> get "{123}"
-> Redirected to slot [5555] located at 10.10.10.102:6379
(nil)
10.10.10.102:6379> scan 0
1) "0"
2) 1) "{123}:ysoftman-test-key1"
10.10.10.102:6379>

로컬에서 k8s redis cluster 접속

# k8s 에 redis cluster 를 구성하였다.
# redis 노드1개는 1개의 pod 로 구성되고, 이 pod 를 서비스로 묶었다.

# 다음과 클라이언트가 k8s 안의 pod(redis-cluster-0) 라면 접속이 잘된다.
kubectl --namespace redis exec -it redis-cluster-0 -- redis-cli -h {레디스 노드 중 하나} -p 6379 -c

# 실제 서비스 서버도 k8s 배포되어 운영시는 문제가 없지만
# 개발시에는 로컬에서 k8s redis 에 접근이 필요하다.
# 그래서 redis 의 k8s service 에 nodeport (32000) 설정하고 
# k8s nodeIP:nodeport 로 접속을 시도 하였다.
redis-cli -h {k8s 노드 중 하나} -p 32000 -c
# 접속은 되지만 set 수행시 연결되지 않고,
set aaa bbb
# 이미 저장된 값을 get 수행하면 moved 로 
# redis pod 로 redirect 시도하고 연결되지 않는다.
get aaa
moved xxxx

# 로컬에서 redis set,get 등 수행시 해당 값을 redis node(slot)으로 분배를 시도하고
# 결국 redis-cluster 의 pod(redis node) ip 에 접근할 수 밖에 없다.

# 해결 방법은 redis-cluster 를 프록시 해주는 redis-cluster-proxy 프로그램을
# k8s service(+pod)에 두고, 이 redis-cluster-proxy 로 요청을 하도록 한다.

# 먼저 redis-cluster-proxy 도커 이미지를 생성한다.
# os 버전이 낮아 gcc 버전이 낮으면 빌드 에러가 발생하니 최신 os 기반에서 빌드하자.
# 자세한 내용은 dockerfile 참고

# 이미 빌드된 redis-cluster-proxy 도커 이미지 사용시 참고


# 이제 redis-cluster-proxy 를 k8s service(+pod)로 다음과 같이 구성한다.
# redis-cluster-proxy.yaml
apiVersion: v1
kind: Pod
metadata:
  name: redis-cluster-proxy
  labels:
    app: redis-cluster-proxy
spec:
  containers:
    - name: redis-cluster-proxy
      image: ysoftman/redis-cluster-proxy:latest
      imagePullPolicy: Always
      command:
        ["/redis-cluster-proxy/src/redis-cluster-proxy", "10.123.123.123:6379"]
---
apiVersion: v1
kind: Service
metadata:
  name: redis-cluster-proxy
  labels:
    app: redis-cluster-proxy
spec:
  # type: ClusterIP
  type: NodePort
  ports:
    - port: 7777
      targetPort: 7777
      nodePort: 30777 # The range of valid ports is 30000-32767
      name: redis-cluster-proxy
  selector:
    app: redis-cluster-proxy

# k8s service(+pod) 반영
kubectl apply -f redis-cluster-proxy.yaml --namespace redis

# 다음과 같은 요청 흐름이 된다.
로컬 redis-cli k8s 노드 중 하나:30777 --> redis-cluster-proxy(service) --> redis-cluster-proxy(pod):7777 --> redis cluster nodes(pods):6379

# 이제 로컬 개발시에는 redis-cluster-proxy 를 통해 k8s redis 를 사용할 수 있다.
redis-cli -h {k8s 노드 중 하나} -p 30777 -c


#####


# helm 으로 외부에서 접근 가능한 redis-cluster 으로 구성할 경우
# 다음과 같이 실행하면 service 3개에 각각 redis pod 1개를 띄우고, 
# 서비스별 external ip 를 생성한다.(시간이 좀 걸린다.)
helm install redis-cluster bitnami/redis-cluster \
--namespace redis-cluster \
--set cluster.node=3 \
--set cluster.externalAccess.enabled=true

# 이후 helm upgrade 설치 가이드 명령 참고해서 
helm upgrade redis-cluster bitnami/redis-cluster \
--namespace redis-cluster \
--set cluster.node=3 \
--set cluster.externalAccess.enabled=true \
--set cluster.externalAccess.service.type=LoadBalancer \
--set "cluster.externalAccess.service.loadBalancerIP[0]=10.10.10.1,cluster.externalAccess.service.loadBalancerIP[1]=10.10.10.2,cluster.externalAccess.service.loadBalancerIP[2]=10.10.10.3"


# 참고


helm command

# helm 은 redis, jenkins, couchbase 등 널리 알려진 패키지(k8s에서는 리소스)을
# kubernetes(k8s) 환경에 쉽게 배포 운영해주는 일종의 패키지(리소스) 매니져다.
# helm 은 charts(k8s 리소스 생성을 위한 설정들 모아놓은 패키지)를 관리하는 방식으로 사용된다.
# kubectl 설치하고
wget https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo cp kubectl /usr/local/bin

# kubectl config 로 context 를 설정해야 한다.
kubectl config set-credentials xxx
kubectl config set-cluster xxx
kubectl config set-context xxx
kubectl config use-context xxx

# 설치 - mac
brew install kubernetes-helm

# 설치 - linux
# github 에 이미 빌드된 helm binary 사용한다.
# https://github.com/helm/helm/releases
# 참고로 helm 서버와 버전이 호환되는 helm client 를 사용해야 한다.
wget https://get.helm.sh/helm-v2.16.1-linux-amd64.tar.gz
tar zxvf helm-v2.16.1-linux-amd64.tar.gz
sudo cp linux-amd64 /usr/local/helm

# Error: incompatible versions 로 클라,서버 버전이 다른 경우
# helm 클라이언트를 서버 버전(2.14.1)으로 설치한다.
curl -L https://git.io/get_helm.sh | bash -s -- --version v2.14.1

# 초기화
# ~/.helm 을 셋팅한다.
helm init

# helm 으로 설치된 리스트
helm list -A

# 패키시 상태(Deployment, Service, Ingress...등)
helm status 이름

# helm chart 저장소 업데이트
helm update repo

# helm redis 설치
# 사용 가능한 redis chart 검색
helm search redis

# chart 상세보기
helm inspect stable/redis

# syntax 체크, -f value.yaml
helm lint stable/redis -f stable/values-dev.yaml

# 템플릿 렌더링 결과
helm template stable/redis -f stable/values-dev.yaml

# 설치 시뮬레이션, 디버그 메시지 출력
helm install stable/redis --name ysoftman-redis -n ysoftman-ns --dry-run --debug

# 설치(redis 클러스터 구성시 다음 명령 실행 후 출력되는 NOTES 부분 참고)
helm install stable/redis --name ysoftman-redis -n ysoftman-ns

# 업데이트
# upgrade 에서는 --history-max 로 헬름으로 배포된 히스토리 내역 최대 개수 제한 할 수있다. 기본(256개)
helm upgrade ysoftman-redis stable/redis -n ysoftman-ns --history-max 3

# values.yaml 의 특정값을 변경해서 업데이트
helm upgrade ysoftman-redis stable/redis -n ysoftman-ns --set ysoftman.key1=aaa,ysoftman.key2=bbb

# NOTES 부분은 status 로 다시 볼 수 있다.
helm status ysoftman-redis

# history 보기
helm history ysoftman-redis

# 롤백
helm rollback ysoftman-redis 1

# 삭제, aliases: uninstall, del, delete, un
helm delete ysoftman-redis

# 커스텀 chart 만들기
# 참고 https://docs.bitnami.com/kubernetes/how-to/create-your-first-helm-chart/
# 기본 디렉토리와 파일들을 생성해 준다.
# 참고로 Go template 엔진을 사용한다.
helm create mychart

# mychart 내의 파일을 수정후 상위 디렉토리로 이동후 커스텀 chart 로 설치할 수 있다.
helm install ./mychart --name ysoftman-app

# helm 으로 관리되는(설치된) 리소스 보기
# k9s > helm 리소스로도 볼 수 있다.
kubectl get all -A -l='app.kubernetes.io/managed-by=Helm'

redis cluster in kubernetes

# kubernetes(k8s) 노드가 다음과 같이 구성되어 있다고 가정하고 설치한다.
1 maser
3 worker

# 참고
https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/
https://kubernetes.io/ko/docs/tutorials/configuration/configure-redis-using-configmap/
https://github.com/sanderploegsma/redis-cluster


# redis-cluster.yaml 내용
apiVersion: v1
kind: Namespace
metadata:
  name: redis-cluster
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
data:
  fix-ip.sh: |
    #!/bin/sh
    CLUSTER_CONFIG="/data/nodes.conf"
    if [ -f ${CLUSTER_CONFIG} ]; then
      if [ -z "${POD_IP}" ]; then
        echo "Unable to determine Pod IP address!"
        exit 1
      fi
      echo "Updating my IP to ${POD_IP} in ${CLUSTER_CONFIG}"
      sed -i.bak -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${CLUSTER_CONFIG}
    fi
    exec "$@"
  # | (newline 유지하는 멀티라인)
  # + (마지막 newline 유지)
  redis.conf: |+
    cluster-enabled yes
    cluster-require-full-coverage no
    cluster-node-timeout 15000
    cluster-config-file /data/nodes.conf
    cluster-migration-barrier 1
    appendonly no
    save ""
    protected-mode no
    requirepass "password123"
    masterauth "password123"

---

apiVersion: v1
kind: Service
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
spec:
  ports:
  - port: 6379
    targetPort: 6379
    name: client
  - port: 16379
    targetPort: 16379
    name: gossip
  # clusterIP 로 k8s 클러스터내부에서만 접근 가능한 Service(pod묶음)을 제공하자.
  type: clusterIP
  # clusterIP 를 명시하지 않으면 Service 시작시 IP 가 자동할당된다.
  #clusterIP: None
  clusterIP: "10.10.10.123"
  selector:
    app: redis-cluster
---
# StatefulSet 은 Pod 집합인 Deployment 와 스케일
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
spec:
  serviceName: redis-cluster
  replicas: 4
  selector:
    matchLabels:
      app: redis-cluster
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      containers:
      - name: redis
        image: redis:6.2-rc
        args: ["--requirepass", "password123"]
        ports:
        - containerPort: 6379
          name: client
        - containerPort: 16379
          name: gossip
        command: ["/conf/fix-ip.sh", "redis-server", "/conf/redis.conf"]
        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_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        volumeMounts:
        - name: conf
          mountPath: /conf
          readOnly: false
        - name: data
          mountPath: /data
          readOnly: false
      volumes:
      - name: conf
        configMap:
          name: redis-cluster
          defaultMode: 0755
  volumeClaimTemplates:  #  PersistentVolumesClaims(PVC) 100MiB 요청(생성)해서 pod 가 내려가도 데이터는 유지될 수 있도록 한다.
  - metadata:
      name: data
      labels:
        name: redis-cluster
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 100Mi

# redis 클러스터 master, worker, statefulset, service 생성
kubectl apply -f redis-cluser.yaml

# configmap, statefulset, service 확인
kubectl describe cm --namespace redis-cluster
kubectl describe sts --namespace redis-cluster
kubectl describe svc --namespace redis-cluster

# statefulset 에서 PersistentVolumeClaim(PVC) 상태 확인
kubectl get pvc --namespace redis-cluster

# POD 및 서비스 상태 확인
kubectl get all --namespace redis-cluster

# 클러스터링 구성
# redis5 부터 redis-cli 에서 redis-trib.rb 의 기능을 사용할 수 있다.
# redis 클러스터 명령 참고
redis-cli --cluster help

# redis 동작중인 pod 파악
kubectl get pods --namespace redis-cluster | grep redis | awk '{print $1}' | head -1

# jsonpath output 을 app=redis 인 pod 들의 IP 파악
# 참고 https://kubernetes.io/docs/reference/kubectl/jsonpath/
kubectl get pods --namespace redis-cluster -l app=redis-cluster -o jsonpath='{range .items[*]}{.status.podIP}{":6379 "}{end}'

# pod ip:port 를 옵션으로 주어 클러스터링을 구성한다.
# cluster-replicas 복제(슬레이브) 개수 4개의 노드라면 2개 마스터, 2개 슬레이브가 된다.
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli --cluster create --cluster-replicas 1 -a "password123" \
$(kubectl --namespace redis-cluster get pods -l app=redis-cluster -o jsonpath='{range .items[*]}{.status.podIP}{":6379 "}{end}')
# Can I set the above configuration? (type 'yes' to accept): yes 입력

# redis cluster info 확인
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -a "password123" cluster info

# redis cluster node 확인
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -a "password123" -c cluster nodes

# redis-cluster-0 파드에서 redis-cli 로 clusterIP(고정) 에 접속해 테스트
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -h 10.10.10.123 -p 6379 -c -a "password123"
10.10.10.123:6379> set aaa lemon
OK
10.10.10.123:6379> get aaa
"lemon"
10.10.10.123:6379> set bbb apple
-> Redirected to slot [5287] located at 10.10.123.100: 6379
OK
10.10.123.100:6379> get bbb
"apple"


####


# 노드 변경이 필요한 경우
# replicas=6 로 노드(pod) 6개로 늘린다.
# 기존보다 작으면 줄어든다. 줄일때는 노드 제거후 적용
kubectl --namespace redis-cluster scale statefulset redis-cluster --replicas=6

# 2추가된 노드에
# 10.10.10.10:6379 기존 존재하는 클러스터 노드에 마스터 노드 추가
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster add-node 10.10.10.11:6379 10.10.10.10:6379

# 10.10.10.11:6379 마스터의 슬레이브가 추가
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster add-node 10.10.10.12:6379 10.10.10.11:6379 --cluster-slave

# 슬롯 재분배
# 새 마스터 노드가 추가 된경우 --cluster-use-empty-masters 사용
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster rebalance 10.10.10.10:6379 --cluster-use-empty-masters

# 노드 빼는 경우(10.10.10.10:6379 노드에 접속해서 aaaaabbbbb 노드들 삭제)
kubectl --namespace redis-cluster \
exec -it redis-clustqer-0 -- \
redis-cli -a "password123" --cluster del-node 10.10.10.12:6379 aaaaabbbbb


####


# confimap 삭제
kubectl delete cm redis-cluster --namespace redis-cluster

# PersistentVolumeClaim(PVC) 삭제
kubectl delete sts redis-cluster --namespace redis-cluster

# pvc 삭제
# kubectl get pvc 로 볼륨이름 파악
kubectl delete pvc 볼륨이름 --namespace redis-cluster
# 또는 전체 삭제하는 경우
kubectl delete pvc --all --namespace redis-cluster

# service 모두 삭제
kubectl delete service --all --namespace redis-cluster

# pod 모두 삭제
kubectl delete pods --all --namespace redis-cluster

# unknown state pod 가 삭제 안되는 경우
kubectl delete pods --all --grace-period=0 --force --wait=false --namespace redis-cluster

# 네임스페이의 모든 리소스 삭제(단 secret,configmap,roels등과 같은 특정 리소스는 명시적으로 삭제해야 한다.)
# all=pods,deployments,rs 등을 의미한다.
kubectl delete all --all --namespace redis-cluster

# namespace 삭제
kubectl delete ns redis-cluster

python redis cluster import error

# python 2.x 기준 redis, redis-py-cluster 를 사용중
# 다음과 같은 ImportError 가 발생했다.
packages/rediscluster/nodemanager.py", line 12, in <module>
    from redis._compat import b, unicode, bytes, long, basestring
ImportError: cannot import name b

# 문제는 redis 3.0.1 일때 발생하였으며
# 다음과 같이 2.x 버전대로 설치하여 해결했다.
pip install redis==2.10.6

# 아래 redis-py release 를 보면 2018-11-15 쯤 릴리즈를 했는데
# python 2.x 에서 문제가 있는것으로 보인다.
https://github.com/andymccurdy/redis-py/releases

redis-server defunct 문제

# redis cluster 서버를 운영 중에 redis 의 상태를 체크하려고
# 프로세스 카운트 아래와 같이 해본다.
ps -ef | grep redis-server | grep -v grep | wc -l

# 그런데 가끔 카운트가 증가되는 경우가 발생한다.
# 증가했을때 ps 를 보면 다음과 같이 defunct(zombie)프로세스가 나타난다.
# (pid ip 등은 임의 값으로 편집한것임)
ysoftman 1234    1  1 Aug30 ? 02:38:07 /home/ysoftman/redis/bin/redis-server 10.10.10.10:7001 [cluster]
ysoftman 555  1234  0 22:22 ? 00:00:00 [redis-server] <defunct>

# 찾아봐도 뚜렷한 원인은 알 수 없었다.
# 위 ps 에서  defunct 의 ppid 가 redis-server 인것을 보면
# redis-server 가 어떤 작업 수행을 위해 임의로 fork 하는것으로 보인다.
# defunct 발생시간에 redis.log 파일을 보면 555 프로세스가 rdb 덤프 작업을 하는것을 알 수 있었다.

10000 changes in 60 seconds. Saving...
Background saving started by pid 555
DB saved on disk
RDB: 0 MB of memory used by copy-on-write
Background saving terminated with success

# .conf 파일에 디폴트로 다음과 같은 조건으로 덤프를 실행하게 된다.
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
save 900 1
save 300 10
save 60 10000

# 어째든 rdb 덤프를 위해서 redis-server 가 백그라운드로 실행하는 프로세스가
# 가끔 defunct 가 될 수 있다.
# 휘발성 데이터라면 다음으로 덤프로 기능을 끌 수 있다.
save ""

redis, kafka 유입 확인

# redis 유입 확인
# 참고 https://redis.io/commands/monitor
# redis 서버라면
redis-clit monitor

# 아니라면 텔넷으로 확인
telnet ysoftman-redis 6379

# 접속 후 monitor 커맨드로 유입 확인
monitor

# 종료
ctrl + ] 후 quit

# 또는 redis-cli 명령 사용
redis-cli -h ysoftman-redis -p 6379 monitor


#####


# kafkacat(kcat) 으로 유입 확인
https://github.com/edenhill/kafkacat
# brew install kafkacat

# 1.7.0 부터 kcat 으로 이름이 변경됨.
# 맥에서 설치
brew install kcat

# -b brocker(kafaka 서버), -t topic(쿼리할 토픽)
kcat -b ysoftman_host:9092 -t my_topic

# -L 메타데이터(브로커,토픽) 리스트 보기
kcat -L -b ysoftman_host:9092

hiredis for windows

hiredis windows 구글링을 하면 윈도우용으로 포팅된것을 사용할 수 있다고 나온다.

MS OpenTech
공식웹 https://msopentech.com
프로젝트 https://github.com/MSOpenTech
에서는 다양한 오픈소스를 윈도우용으로 포팅하여 제공해주고 있다.
redis 는 공식적으로 리눅스만 지원하지만 MSOpenTech 에서 윈도우용으로 포팅된 Redis 를 제공한다. Redis 프로젝트내에 hiredis 가 있는데 이것을 사용하기 위함이다.

# 윈도우용 redis 다운 받기
git clone https://github.com/MSOpenTech/Redis

# hiredis library 빌드 하기
msvs\RedisServer.sln 실행
hiredis -> 속성 -> 구성 -> 일반 -> 플랫폼 도구 집합 -> visual studio 버전 선택 후 빌드

# vs2012 이전 버전에서는 'winapifamily.h' 에러가 발생하여
C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ 경로 추가

# hiredis 자체는 lib 빌드는 잘 된다.
msvs\x64\Debug\hiredis.lib
msvs\x64\Release\hiredis.lib

# 하지만 프로젝트에 hiredis.lib 사용하려면 문제가 발생한다.
# 우선 소스 경로 추가
src\
deps\hiredis

# vs2010 의 경우 우선 'winapifamily.h' 에러가 발생한다.
# C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ 경로 추가하면 이부분은 해결된다.

오류 1 error C1083: 포함 파일을 열 수 없습니다. 'winapifamily.h': No such file or directory c:\users\ysoftman\downloads\redis-win-2.8.17.3\src\win32_interop\WS2tcpip.h 51

# vs2010, vs2012 모두 매크로 중복 정의 에러가 발생한다.
오류 1 error C4005: 'ECONNRESET' : 매크로 재정의 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\errno.h 100
오류 2 error C4005: 'EINPROGRESS' : 매크로 재정의 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\errno.h 104
오류 3 error C4005: 'ETIMEDOUT' : 매크로 재정의 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\errno.h 130

결론적으로 MSOpenTehc 에서 제공하는 hiredis 윈도우용으로 사용 실패다.
최신 버전을 바로 사용할 수도 없고 vs 버전별 설정해야할 것도 다른것을 봤을때 MSOpenTech hiredis windows 빌드는 왠만하면 쓰지 않는 것이 정신건강에 좋을 것 같다.ㅠ

대신 hiredis 자체를 VS 프로젝트로 포팅한 것을 발견했다.
https://github.com/texnician/hiredis-win32

redis - hiredis(Client C) 예제

#include "../../hiredis/hiredis.h"

// hiredis 설치
// git clone https://github.com/redis/hiredis.git
// cd hiredis
// make
// sudo make install
// ldconfig
// 빌드시 makefile 에 -lhiredis 옵션 추가


//hiredis 를 이용한 redis 테스트(hiredis exmaple 소스 발췌)
int main()
{
    unsigned int j;
    redisContext *c;
    redisReply *reply;
    const char *hostname = "127.0.0.1";
    int port = 6379;

    struct timeval timeout = { 1, 500000 }; // 1.5 seconds
    c = redisConnectWithTimeout(hostname, port, timeout);
    if (c == NULL || c->err) {
        if (c) {
            printf("Connection error: %s\n", c->errstr);
            redisFree(c);
        } else {
            printf("Connection error: can't allocate redis context\n");
        }
        exit(1);
    }

    /* PING server */
    reply = (redisReply*)redisCommand(c,"PING");
    printf("PING: %s\n", reply->str);
    freeReplyObject(reply);

    /* Set a key */
    reply = (redisReply*)redisCommand(c,"SET %s %s", "foo", "hello world");
    printf("SET: %s\n", reply->str);
    freeReplyObject(reply);

    /* Set a key using binary safe API */
    reply = (redisReply*)redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
    printf("SET (binary API): %s\n", reply->str);
    freeReplyObject(reply);

    /* Try a GET and two INCR */
    reply = (redisReply*)redisCommand(c,"GET foo");
    printf("GET foo: %s\n", reply->str);
    freeReplyObject(reply);

    reply = (redisReply*)redisCommand(c,"INCR counter");
    printf("INCR counter: %lld\n", reply->integer);
    freeReplyObject(reply);
    /* again ... */
    reply = (redisReply*)redisCommand(c,"INCR counter");
    printf("INCR counter: %lld\n", reply->integer);
    freeReplyObject(reply);

    /* Create a list of numbers, from 0 to 9 */
    reply = (redisReply*)redisCommand(c,"DEL mylist");
    freeReplyObject(reply);
    for (j = 0; j < 10; j++) {
        char buf[64];

        snprintf(buf,64,"%d",j);
        reply = (redisReply*)redisCommand(c,"LPUSH mylist element-%s", buf);
        freeReplyObject(reply);
    }

    /* Let's check what we have inside the list */
    reply = (redisReply*)redisCommand(c,"LRANGE mylist 0 -1");
    if (reply->type == REDIS_REPLY_ARRAY) {
        for (j = 0; j < reply->elements; j++) {
            printf("%u) %s\n", j, reply->element[j]->str);
        }
    }
    freeReplyObject(reply);

    /* Disconnects and frees the context */
    redisFree(c);

    return 0;
}

Redis Windows 용 포팅

공식적으로 Redis 는 윈도우를 지원하지 않는다.
그래서 MS 의 오픈소스 관련 자회사애서 Redis 를 윈도우용을 포팅하여 배포하고 있다.

윈도우 용 Redis 를 다운

Redis Java Client Jedis 테스트

// ysoftman
// Redis Java Client Connector - jedis 사용
// jar 다운로드 https://github.com/xetorthio/jedis/downloads
import redis.clients.jedis.Jedis;

public class RedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// jedis.auth("password");

System.out.println("jedis.dbSize() = " + jedis.dbSize());

// set 해보기
System.out.println("jedis.set(\"nickname\", \"Hello Redis~\") = " + jedis.set("nickname", "Hello Redis~"));

// get 해보기
System.out.println("jedis.get(\"nickname\") = " + jedis.get("nickname"));
}
}

Redis 시작하기

# 소스로 설치
# Redis 다운 받기(윈도우는 비공식적으로 지원하지만 불안정하다고 한다.)
# wget http://redis.googlecode.com/files/redis-2.6.14.tar.gz
tar zxvf redis-2.6.14.tar.gz
cd redis-2.6.14

# /deps/jemalloc/lib/libjemalloc.a 찾을 수 없다는 에러가 발생하면
# make distclean 후 make
make
sudo make install

# Redis 2.6 버전 make 실행시 다음과 같은 에러 발생하는 경우가 있다.
/usr/local/redis/src/zmalloc.c:223: undefined reference to `__sync_add_and_fetch_4'

# 현재 cpu 설정이 맞지 않아 발생한 것으로 우선 uname 으로 자신 cpu 타입을 확인한다.
uname -a

# i686 인 경우 다음과 같이 make 설정을 변경하고 make 하자
vi src/.make-settings
OPT=-O2 -march=i686

##########

# 2018-02 현재 4.0 을 사용
wget http://download.redis.io/releases/redis-4.0.8.tar.gz
tar zxvf redis-4.0.8.tar.gz
cd redis-4.0.8
make
sudo make install

##########

# redis 소스에 redis-4.0.8/src/redis-trib.rb 로 클러스터를 설정한다.
# redis-trib.rb 를 사용하기 위해 최신 버전의 ruby 설치
wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.0.tar.gz
tar zxvf ruby-2.5.0.tar.gz
cd ruby-2.5.0
./configure
make
sudo make install

# gem(ruby)에서 사용할 redis 설치
sudo gem install redis

##########

# Redis 서버 실행
redis-server

# Redis 설정 파일로 실행
redis-server /etc/redis.conf

# Redis 서버 데몬으로 실행, 보호모드 비활성화
# 보호모드가 활성화 되면 로컬외 외부 클라이언트가 shutdown 할수 없음.
redis-server --daemonize yes --protected-mode no

# 기본 redis.conf 복사
sudo /etc/redis.conf ./
chown ysoftman:ysoftman ./redis.conf

# 클러스터 구성을 위해서 최소 3개의 노드(redis-server 인스턴스)가 있어야 한다.
# 포트 설정 별로 디렉토리 생성
mkdir 7001 7002 7003

##########

# cluster 구성
vi redis-7001.conf
port 7001
# bind ip 변경
# 원격으로 접속한다면 실제 IP 를 추가하자.
# 127.0.0.1 을 처음으로 명시하면 redis-server 가 127.0.0.1 으로 시작되어 cluster 생성시 원격 노드 조인 작업이 끝나지 않는다.
# 관련 이슈 https://github.com/antirez/redis/issues/1703#issuecomment-369822404
bind 10.10.10.1 127.0.0.1

# 클러스터 활성화
cluster-enabled yes

# 클러스터 노드 상태를 기록하는 파일로 노드가 자동 생성하고 사용자 수정하면 안된다.
cluster-config-file /home/ysoftman/7001/nodes-7001.conf

# 클러스터 노드 타임아웃 : 단위는 ms, 노드가 살아있는지/내려갔는지 체크 
cluster-node-timeout 15000

# 워킹 디렉토리 설정
dir /home/ysoftman/7001
# 로그 파일 위치
logfile /home/ysoftman/7001/redis_7001.log
# pid 파일 위치
pidfile /home/ysoftman/7001/redis_7001.pid
# redis 는 다음 2가지 모드로 파일로 백업한다.

# append 모드 : 노드가 내려갔다 다시 시작할때 데이터를 추가하는 방식으로 설정
appendonly yes

# rdb 모드 : 파일명(경로가 아닌 파일명만 명시해야 한다. 경로는 dir 로 설정된 곳을 사용한다.)
dbfilename dump_7001.rdb
# append 모드 비활성화
appendonly no
# rdb 모드 비활성화
save ""

##########

# 위 설정으로 포트별로 디렉토리에 복사하고 포트별로 redis-server 시작
./bin/redis-server ./7001/redis-7001.conf
./bin/redis-server ./7002/redis-7002.conf
./bin/redis-server ./7003/redis-7003.conf

http://redisgate.kr/redis/cluster/redis-trib.php
# 별도의 장비(10.10.10.2)도 위처럼 설정하여 클러스터의 노드로 추가할 수 있다.
# slave 노드는 master 내용을 복제해두며 replicas 로 설정할 수 있다.
# --replicas n 으로 master 당 사용할 slave 개수를 지정한다.
# 그러면 현재 노드들에서 적절한 master - slave 를 구성한다.
# 디폴트는 --replicas 0 으로 모든 노드가 master 가 된다.
# echo yes 는 (type 'yes' to accept) 를 자동 입력하기 위해 사용
echo yes | ./redis-4.0.8/src/redis-trib.rb create --replicas 1 10.10.10.1:7001 10.10.10.1:7002 10.10.10.1:7003 10.10.10.2:7001 10.10.10.2:7002 10.10.10.2:7003

# 10.10.10.1:7001 노드에 10.10.10.1:7004 노드 master 로 추가
redis-trib.rb add-node 10.10.10.1:7004 10.10.10.1:7001

# slave 로 추가시
redis-trib.rb add-node --slave 10.10.10.1:7004 10.10.10.1:7001

# 10.10.10.1:7004 노드 삭제
redis-trib.rb del-node 10.10.10.1:7004 노드_ID

# ERR Slot 0 is already busy (Redis::CommandError) 에러 발생시
# cluster 를 다시 시작할 경우 기존 .rdb node*.conf .aof 파일은 삭제해야 한다.
# redis 시작시 설정에 따라 .rdb 또는 .aof 파일로 복구를 시도하면 에러가 발생할 수 있으니 필요없다면 삭제하도록 한다.
find . -name dump*.rdb -exec rm -rfv {} \;
find . -name nodes-*.conf -exec rm -rfv {} \;
find . -name *.aof -exec rm -rfv {} \;

##########

# redis 5.0 부터는 redis-cli 에서 redis-trib.rb 의 기능을 사용할 수 있다.
# Redis 동작 확인
redis-cli -h 10.10.10.1 -p 7001 ping

# 클러스터를 사용하는 경우 -c 옵션 사용
# moved x 는 호스트x에 데이터가 저정되어 있다는 의미고
# ask x 는 호스트x에 다시 물어(질의)야 한다는 의미다.
# -c 는 moved, ask 대상을 따라가서 (질의하게) 된다.
redis-cli -h 10.10.10.1 -p 7001 -c

# 키 scan
redis-cli -h 10.10.10.1-p 7001 -c --scan

# 특정패턴의 키 scan
redis-cli -h 10.10.10.1-p 7001 -c --scan --pattern "*ysoftman"

# 또는 다음처럼 접속해서 커맨드만 실행시켜 끝낼 수도 있다.
redis-cli -h 10.10.10.1 -p 7001 -c cluster info

# 노드 정보(myself 표시는 현재 접속한 노드), ip 로 정렬해서 보자.
redis-cli -h 10.10.10.1 -p 7001 -c cluster nodes | sort -k 2

# 노드 정보(myself 표시는 현재 접속한 노드)
# slave 에 master 노드 id 가 명시되어있어
# master-slave 연결 순서로 정렬해서 보자.
redis-cli -h 10.10.10.1 -p 7001 -c cluster nodes | sort -k 4
# 노드별 할당된 슬롯 정보
redis-cli -h 10.10.10.1 -p 7001 -c cluster slots

# ysoftman으로 시작하는 키만 노드에서 삭제하기
# 각 노드에 del 커맨드를 실행해야 한다.
# del 는 정규식이나 와일드카드를 사용할수 없고 exact 키 매칭이 되야 한다.
# 따라서 scan, pattern 으로 키이름을 파악해 xargs 로 넘겨야 한다.
# 삭제시 에러가발생하면 조회한 key를 파일로 저장해 하나씩 삭제하는 스크립트를 작성하자.
redis-cli -h 10.10.10.1 -p 7001 -c --scan --pattern "ysoftman*" | xargs redis-cli -h 10.10.10.1 -p 7001 -c del
redis-cli -h 10.10.10.1 -p 7002 -c --scan --pattern "ysoftman*" | xargs redis-cli -h 10.10.10.1 -p 7002 -c del
redis-cli -h 10.10.10.1 -p 7003 -c --scan --pattern "ysoftman*" | xargs redis-cli -h 10.10.10.1 -p 7003 -c del

##########

# 클러스터 정보 확인
redis>cluster info

# 클라이언트에서 set
redis>set nickname ysoftman

# 클라이언트에서 get
redis>get nickname

# Redis 서버 종료
# protected-mode 가 비활성화된 redis-server 만 가능
redis>shutdown

# 또는 각 서버 redis-cli 로 종료
redis-cli -h 10.10.10.1 -p 7001 shutdown
redis-cli -h 10.10.10.1 -p 7002 shutdown
redis-cli -h 10.10.10.1 -p 7003 shutdown

##########

# 장애 복구 과정(failover)
# 10.10.10.1:7001 master 노드가 죽었을때(kill -9 PID)
# 10.10.10.1:7002 slave 노드를 master 로 변환
redis-cli -h 10.10.10.1 -p 7002 -c cluster failover

# 10.10.10.1:7001 master 죽은 노드를 클러스터에서 제거
# 60초 안에 모든 노드들에서 forget 을 실행주지 않으면 정상 노드가
# 연결을 시도하기 때문에 죽은 노드가 handshake 상태가 된다.
# handshake 상태의 죽은 노드는 계속해서 id 가 변경된다.
# 다음과 같이 한번에 살아 있는 노드에서 죽은 노드를 제거해야 한다.
# 죽은 노드가 n개라면 n번만큰 살아 있는 노드에서 forget 해야 한다.
redis-cli -h 10.10.10.1 -p 7002 -c cluster forget 죽은_노드_ID
redis-cli -h 10.10.10.1 -p 7003 -c cluster forget 죽은_노드_ID
... 여러 노드에서 죽은 노드 만큼 수행

# 계속 변하는 handshake 노드도 다음의 쉘 스크립트로 한번에 정리할 수 있다.
https://github.com/antirez/redis/issues/2965#issuecomment-233779778
# 죽은 노드를 정리하고 클러스터 상태를 보면
# 죽은 노의 슬롯들이 처리되지 않아 클러스터 상태가 fail 로 나온다.
redis-cli -h 10.10.10.1 -p 7001 -c cluster nodes
cluster_state:fail

# 죽은 노드가 관리하던 슬롯(nodes 정보로 봤을때 마지막 필드에 나온 숫자-숫자)
# 참고로 슬롯범위를 알지 못할때 실패 슬롯 개수를 파악하고
# 노드들을 slots 로 정렬하고 연속적이지 않은 부분이나, 끝이
# cluster_slots_fail 개수 만큼이 범위가 된다.
redis-cli -h 10.10.10.1 -p 7001 -c cluster info | grep cluster_slots_fail
redis-cli -h 10.10.10.1 -p 7001 -c cluster nodes | sort -k 9

# 슬롯들을 7001 에 추가(이관)해준다.
# 위에서 죽은 노드를 빼줘야지 슬롯을 이관할 수 있다.
# 참고로 {a..b} 범위는 쉘 명령으로 스크립트파일.sh 을 만들어 사용하는 경우
# 아래 명령줄 앞에 eval 을 사용해야 한다.

redis-cli -h 10.10.10.1 -p 7001 -c cluster addslots {10000..12000}
# 노드를 다시 추가할 경우
# redis 노드 시작
./bin/redis-server ./7003/redis-7003.conf

# 신규 노드가 기존 노드(ip로 명시해야 한다.)를 meet 하도록 한다.
redis-cli -h 10.10.10.1 -p 7003 -c cluster meet 10.10.10.1 7001

# addslot 대신 reshard 로 슬롯을 재분배 할 수 도 있다.
# redis 4.x 는 redis-trib.rb rehsard 사용
redis-cli -h 10.10.10.1 -p 7001 -c cluster reshard