Redis: Master-Slave репликация и запуск в Kubernetes

Автор: | 23/09/2020

Задача – запустить Redis в Kubernetes.

Используем Master-Slave репликацию и Sentinel для мониторинга и failover.

См. Redis: репликация, часть 2 — Master-Slave репликация, и Redis Sentinel.

Redis cluster vs Redis replication

См. Redis: репликация, часть 1 — обзор. Replication vs Sharding. Sentinel vs Cluster. Топология Redis и Choose between Redis Helm Chart and Redis Cluster Helm Chart.

Вкратце:

  • Replica – включает в себя Redis Master, который выполняет чтение-запись, и копирует данные на Redis Slaves, которые обслуживает запросы на чтение. При этом слейвы могут заменить мастера, если он упадёт
  • Cluster – имеет смысл, если у вас данных больше, чем памяти на отдельном сервере. Кластер умеет Sharding, и клиент, запрашивающий определённый ключ, будет направлен на ту ноду, которая этот ключ хранит.

Варианты запуска Redis в Kubernetes

Что надо – это Redis с репликацией.

Как можно реализовать:

Что нам упрощает жизнь, так это то, что нам не нужна персистентность данных – наш Redis будет использоваться толкьо для хранения кеша, который при рестарте можно терять, а значит – не будет лишней головной боли с Kubernetes PersistentVolume.

Helm chart deploy

Сначала запустим все сервисы, посмотрим на них, потом перейдём к доступным опциям.

Добавляем репоизторий Bitnami:

[simterm]

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories

[/simterm]

Деплоим Redis:

[simterm]

$ helm install backend-redis bitnami/redis
NAME: backend-redis
LAST DEPLOYED: Tue Sep 22 14:48:02 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
Redis can be accessed via port 6379 on the following DNS names from within your cluster:

backend-redis-master.default.svc.cluster.local for read/write operations
backend-redis-slave.default.svc.cluster.local for read-only operations


To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace default backend-redis -o jsonpath="{.data.redis-password}" | base64 --decode)

To connect to your Redis server:

1. Run a Redis pod that you can use as a client:
   kubectl run --namespace default backend-redis-client --rm --tty -i --restart='Never' \
    --env REDIS_PASSWORD=$REDIS_PASSWORD \
   --image docker.io/bitnami/redis:6.0.8-debian-10-r0 -- bash

2. Connect using the Redis CLI:
   redis-cli -h backend-redis-master -a $REDIS_PASSWORD
   redis-cli -h backend-redis-slave -a $REDIS_PASSWORD

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/backend-redis-master 6379:6379 &
    redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD

[/simterm]

Получаем пароль, который был сгенерирован во время установки чарта:

[simterm]

$ kubectl get secret --namespace default backend-redis -o jsonpath="{.data.redis-password}" | base64 --decode
TySS43UhAW

[/simterm]

Запускаем port-forward, что бы подключиться к поду с Redis master:

[simterm]

$ kubectl port-forward --namespace default svc/backend-redis-master 6379:6379
Forwarding from [::1]:6379 -> 6379
Forwarding from 127.0.0.1:6379 -> 6379

[/simterm]

Подключаемся:

[simterm]

$ redis-cli -h 127.0.0.1 -p 6379 -a TySS43UhAW
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>

[/simterm]

“It works!” (с)

Проверяем сеть:

[simterm]

$ kk get svc redis-service
NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
redis-service   NodePort   172.20.119.242   <none>        6379:32445/TCP   117d

[/simterm]

NodePort, а нам требуется LoadBalancer.

Кроме того – нам требуется Redis Sentinel, а по дефолту он:

sentinel.enabled Enable sentinel containers false

Окей, переходим к опциям – включим Sentinel и LoadBalancer.

Redis Options

Что нам потребуется из опций:

  • global.redis.password
  • metrics:
    • metrics.enabled
    • metrics.serviceMonitor.enabled
    • metrics.serviceMonitor.namespace
  • Persiste:
    • master.persistence.enabled
    • slave.persistence.enabled
  • Service
    • master.service.type
    • master.service.annotations
    • slave.service.type
    • slave.service.annotations
  • Sentinel
    • sentinel.enabled
    • sentinel.service.type – need for LBso clients can ask for Master/Slaves
    • sentinel.service.annotations

Создаём файл с параметрами ~/Temp/redis-opts.yaml:

global:
  redis:
    password: "blablacar"

metrics:
  enabled: true
  serviceMonitor:
    enabled: true
    namespace: "monitoring"

master:
  persistence:
    enabled: false
  service:
    type: LoadBalancer
    annotations: 
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internal 

slave:
  persistence:
    enabled: false
  service:
    type: LoadBalancer
    annotations:
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internal

sentinel:
  enabled: true
  service:
    type: LoadBalancer
    annotations: 
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internal

Обновляем деплоймет, через -f указываем файл с параметрами:

[simterm]

$ helm upgrade --install backend-redis bitnami/redis -f ~/Temp/redis-opts.yaml

[/simterm]

Проверим пароль:

[simterm]

$ kubectl get secret --namespace default backend-redis -o jsonpath="{.data.redis-password}" | base64 --decode
blablacar

[/simterm]

Лоад-балансеры:

[simterm]

$ kk get svc -l app=redis
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP                                                           PORT(S)                          AGE
backend-redis            LoadBalancer   172.20.204.235   af5e1294a4a73426692c7e25f7bb947d-915967.us-east-2.elb.amazonaws.com   6379:30647/TCP,26379:32523/TCP   80s
backend-redis-headless   ClusterIP      None             <none>                                                                6379/TCP,26379/TCP               80s
backend-redis-metrics    ClusterIP      172.20.66.2      <none>                                                                9121/TCP                         80s

[/simterm]

Коннект:

[simterm]

$ redis-cli -h af5e1294a4a73426692c7e25f7bb947d-915967.us-east-2.elb.amazonaws.com -a blablacar
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
af5e1294a4a73426692c7e25f7bb947d-915967.us-east-2.elb.amazonaws.com:6379>

[/simterm]

Работает.

Но почему LoadBalancer публичный, если указывали тип internal?

А потому что аннотация alb.ingress.kubernetes.io/scheme: internal – для ALB Ingress Controller, а мы создаём простой Kubernetes Service с типом LoadBalancer, который создаёт AWS Classic Load Balancer.

Читаем доку https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer, обновим аннотации: вместо “alb.ingress.kubernetes.io/scheme: internal” – указываем “service.beta.kubernetes.io/aws-load-balancer-internal: "true"“:

...
master:
  service:
    type: LoadBalancer
    annotations: 
      service.beta.kubernetes.io/aws-load-balancer-internal: "true"

slave:
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-internal: "true"

sentinel:
  enabled: true
  service:
    type: LoadBalancer
    annotations: 
      service.beta.kubernetes.io/aws-load-balancer-internal: "true"

Обновляекм, проверяем:

[simterm]

$ kk get svc -l app=redis
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP                                                                        PORT(S)                          AGE
backend-redis            LoadBalancer   172.20.178.72   internal-***-1786016015.us-east-2.elb.amazonaws.com   6379:31192/TCP,26379:32239/TCP   17s
backend-redis-headless   ClusterIP      None            <none>                                                                             6379/TCP,26379/TCP               17s
backend-redis-metrics    ClusterIP      172.20.35.163   <none>                                                                             9121/TCP                         17s

[/simterm]

Проверим запись данных – должен пойти через Master-инстанс:

[simterm]

admin@bttrm-dev-app-1:~$ redis-cli -h internal-***-1786016015.us-east-2.elb.amazonaws.com -p 6379 -a blablacar SET testkey testvalue
OK

[/simterm]

Чтение данных:

[simterm]

admin@bttrm-dev-app-1:~$ redis-cli -h internal-***-1786016015.us-east-2.elb.amazonaws.com -p 6379 -a blablacar GET testkey 
"testvalue"

[/simterm]

Статус репликации:

[simterm]

admin@bttrm-dev-app-1:~$ redis-cli -h internal-***-1786016015.us-east-2.elb.amazonaws.com -p 6379 -a blablacar info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.3.50.119,port=6379,state=online,offset=144165,lag=1
...

[/simterm]

И статус Sentinel:

[simterm]

admin@bttrm-dev-app-1:~$ redis-cli -h internal-***-1786016015.us-east-2.elb.amazonaws.com -p 26379 -a blablacar info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=10.3.33.107:6379,slaves=1,sentinels=2

[/simterm]

Хотя вообще, запись на Мастер должна выполняться через получения адреса Мастера у одного из Сентинелов:

[simterm]

admin@bttrm-dev-app-1:~$ redis-cli -h internal-***-1786016015.us-east-2.elb.amazonaws.com -p 26379 -a blablacar sentinel get-master-addr-by-name mymaster
1) "10.3.33.107"
2) "6379"

[/simterm]

См. документацию тут>>>.

Готово.