Redis-cluster в docker swarm

Создание кластера

Redis-cluster рекомендуется делать минимум из 6 нод: 3 мастера, 3 слэйва. На выходе получается шардированный кластер, где каждый из мастеров содержит равное кол-во хэш слотов. При желании кластер легко масштабируется.

Но не всегда есть под рукой 6 нод, которые по факту должны быть на 6-ти разных физических машинах. Допустим, тот же sentinel можно легко разместить на 3х: 3 сентинеля, 1 мастер и 2 слейва. В такой конфигурации падение любой из нод не ведет к краху системы, в случае падения ноды с мастером, оставшиеся 2 сентинеля выберут нового.

Но подумав немного, можно и redis-cluster разместить на 3х нодах: нужно только мастера и слева держать на разных узлах. - server1 (192.168.100.101) - master1 - slave2 - server2 (192.168.100.102) - master2 - slave3 - server3 (192.168.100.103) - master3 - slave1 например вот так. Для слейвов можно сделать еще один systemd unit и пускать на другом порту (например 6380) с другим конфигом. Минимальный конфиг достаточно прост и различие между мастером и слейвом в данном случае будет только в номере порта.

port 6379
timeout 0
tcp-keepalive 300
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

Обратите внимание на то, что редис сам дописывает свои конфиги, в частности nodes.conf.

И затем просто создать кластер:

redis-cli --cluster create 192.168.100.101:6379 192.168.100.102:6379 192.168.100.103:6379

Сразу уточню нюанс - при создании кластера нужно указывать IP, не хочет кластер создаваться на именах.

Коннектимся к любой мастер ноде в кластере и выясняем ID наших мастеров

redis-cli -h master1 cluster nodes

Прикручиваем слейвы к нужным мастерам

redis-cll --cluster add-node 192.168.100.102:6380 192.168.100.101:6379 --cluster-slave --cluster-master1-id
redis-cll --cluster add-node 192.168.100.103:6380 192.168.100.102:6379 --cluster-slave --cluster-master2-id
redis-cll --cluster add-node 192.168.100.101:6380 192.168.100.103:6379 --cluster-slave --cluster-master3-id

Ну вот все в целом кластер готов.

Создание кластера в docker swarm

Идея завести кластер в swarm казалось весьма привлекательной - не нужно установливать, конфигурить и разбираться с. правами на всех нодах. Нужно только правильно раскидать слейвов. Это оказалось не совсем тривиально из-за нескольких причин. Разберем то, что получилось:

1) я собрал отдельный имэдж сразу с конфигом кластера erlong15/redis-cluster 2) docker-compose.yml

version: '3.5'
services:
  master:
    image: erlong15/redis-cluster
    hostname: "{{.Service.Name}}-{{.Task.Slot}}"
    ports:
      - mode: host
        protocol: tcp
        published: 6379
        target: 6379
    deploy:
      mode: replicated
      replicas: 3
      endpoint_mode: dnsrr
    networks:
      - redis
  slave:
    image: erlong15/redis-cluster
    hostname: "{{.Service.Name}}-{{.Task.Slot}}"
    ports:
      - mode: host
        protocol: tcp
        published: 6380
        target: 6379
    deploy:
      replicas: 3
      endpoint_mode: dnsrr
    networks:
      - redis
networks:
  redis:
    driver: overlay
    attachable: true

В конфиге я попытался пробросить порты наружу чтобы цепляться к кластеру из вне. Сразу скажу, что это плохая идея. Кластер при переадресации отдает внутренние адреса поэтому ничего не работает. Попытка сразу собрать кластер на проброшенных портах также не увенчалась успехом, полагаю по той же причине. Поэтому можно эти пробросы из конфига выкинуть.

docker stack deploy -c docker-compose.yml redis
docker run -it --rm --net cluster_redis redis:5 bash -c "echo "yes\n" | redis-cli --cluster create \$(getent hosts tasks.master | awk '{print \$1 \":6379\"}')"

Предполагается, что сварм у вас уже построен. getent hosts tasks.master - так мы получаем все IP относящиеся к сервису master

VAR=($(docker node ps $(docker node ls -q) --filter desired-state=Running --format "{{.Node}} {{.Name}}"| uniq | sort))
docker run -it --rm --net cluster_redis -v /home/redis-cluster-swarm:/data redis:5 bash entrypoint.sh ${VAR[@]}

Первой командой я формирую массив на какой ноде какой контейнер запущен. и передаю этот массив во вторую команду в которой запускаю базовый редис контейнер с entrypoint.sh следующего содержания

VAR=("$@")
#echo ${VAR[2]}

function add_slave() {
if  getent hosts $MASTER_IP | grep -q $2
then
echo "redis-cli --cluster add-node $1:6379 $MASTER_IP:6379 --cluster-slave --cluster-master-id ${master_ids[$MASTER_IP]}"
fi
}

eval declare -A master_ids=($(redis-cli -h master cluster nodes | awk -F":| " '{printf "[\"%s\"]=\"%s\"\n", $2, $1}'))
eval declare -A master_names=($(getent hosts $(getent hosts tasks.master | awk '{print $1}') | perl -lne '/^([\d\.]+)\s+(cluster_master.[0-9]?).*$/ && printf("[\"%s\"]=\"%s\"\n", $2, $1);'))
eval declare -A SLAVES=($(getent hosts $(getent hosts tasks.slave awk '{print $1}') | perl -lne '/^([\d\.]+)\s+(cluster_slave.[0-9]?).*$/ && printf("[\"%s\"]=\"%s\"\n", $2, $1);'))
echo ${master_names[@]}
echo ${SLAVES[@]}

for MASTER_IP in ${!master_ids[@]}
do
add_slave ${SLAVES[${VAR[11]}]} ${master_names[${VAR[1]}]}
add_slave ${SLAVES[${VAR[3]}]} ${master_names[${VAR[5]}]}
add_slave ${SLAVES[${VAR[7]}]} ${master_names[${VAR[9]}]}
done

Здесь я парсю имена контейнеров, определяю их ипишники и отрисовываю команды для добавления slave-nodes с привязкой к правильному мастеру.

Почему так сложно:

Казалось бы в документации по сварму указано, что если у вас сервис называется my-web то все контейнеры будут доступны по именам my-web-1, my-web2, my-web-n в зависимости от фактора репликации. Но так у меня сработало. К имени привязался еще и хэш ноды. Поэтому пришлось регэкспами обрезать только то что нужно.

Выводы

Если у вас меньше 6-ти физически разделенных нод redis-cluster я бы делать не рекомендовал. В этом случае сентинель подходит больше.

Делать в сварме в принципе кластер можно, но с учетом что все остальные сервисы которые работают с этим кластером. находятся также внутри сварма. Кроме того под вопросом автоматическое пересоздание контейнеров при крахе ноды. В описанном варианте они создадутся заново, но к кластеру не подцепятся - это придется делать руками.

Как разберусь с кубернетесом, попробую реализовать такую конфигурацию на нем. Надеюсь там будет проще.

Автор
0 комментариев
Для комментирования необходимо авторизоваться