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\"}')"
Предполагается, что сварм у вас уже построен.
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, попробую реализовать такую конфигурацию на нём. Надеюсь, там будет проще.