Redis-cluster в docker swarm | OTUS

Redis-cluster в docker swarm

Linux_Deep_17.07_site-5020-6c9283.png

При создании кластера 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, я бы делать не рекомендовал. В этом случае сентинель подходит больше.

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

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

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

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