Привет, я Виталий Ранн, занимаюсь в Cloud.ru всем, что связано с брокерами и кешами. Сегодня я хочу закопаться в вопрос о том, в каких случаях self-hosted Redis — это нужно, правильно и неизбежно, а в каких — это победа мастерства над здравым смыслом. Те, с кем мы виделись на конференции GoCloud, уже знают, о чем пойдет речь, а если вы пропустили, знакомьтесь. Вас ждет история DevOps’a Алексея, который столкнулся с проблемой падения Redis в своей компании. Наш герой виртуозно использовал StatefulSet, Redis Sentinel, Helm-чарты Bitnami и другие инструменты, что позволило ему сократить время деплоя и снизить количество сбоев на 70%. Но захочется ли повторить его путь другим — большой вопрос.
Статья будет особенно полезна тем, кто только пробует Redis на вкус и еще не определился, в каком формате с ним стоит взаимодействовать: self-hosted или managed PaaS.

Пролог, в котором мы разбираемся зачем вообще используют Redis
Эта часть для тех, кто никогда не сталкивался с Redis, если вы в курсе — смело листайте к следующему разделу, в котором и начинается наш героический эпос.
Итак, предположим, у вас есть сайт или приложение, которое работает с базой данных: например, списком товаров. Если вы сталкивались реляционными базами данных, то знаете: из-за особенностей чтения и записи работают они не очень быстро и чем больше запросов и массивнее база, тем медленнее вы получите ответ. Что же делать? Решение просто как все гениальное: использовать промежуточный слой в виде намного более быстрой NoSQL базы, куда сохраняется то, что пользователь уже спрашивал. В итоге сервер работает только с новыми запросами, Redis отвечает на частые — скорости восхитительны, пользователи довольны, новые заказы оформляются, пользователи не «отваливаются» из-за долгого ожидания или истечения срока хранения сессии запроса.
Но на этом простор применения инструмента не ограничивается: например, Redis можно использовать как брокер сообщений, с помощью которого можно реализовать очереди задач. Это как диспетчер, который принимает заказы и распределяет их между сотрудниками, следя за тем, чтобы все запросы пошли по адресу и ни один не остался неотвеченным.
Итак, что же случилось у Алексея.
Глава 1. Битва с хаосом
Итак, наш герой, перспективный DevOps в сфере e-commerce, получил сообщение от коллеги: «Алексей, кеш упал. Пользователи ругаются. Fix now. Тимлид в слаке.». То есть задача классическая: увеличить аптайм уже существующего Redis’a и уменьшить задержки, а то раз в месяц Redis стабильно падает, главным образом — из-за человеческих ошибок. Алексей начал всматриваться в бездну этой задачи и вот что обнаружил:
Redis был развернут вручную поверх Bare Metal-сервера, на скриптах, написанных в замшелом 2015-ом человеком, который уже давно уволился.
Решение не кроссплатформенное. Из-за особенностей реализации скриптов, работает каким-то чудом только на конкретном оборудовании, которое стоит в серверной. Серверы старые и не могут скалироваться по ресурсам, давно не выдерживают нагрузку.
Никакого фейловера нет, кластерного режима тоже нет. Это значит, что при падении мастер-ноды, нагрузка не переключается на другую ноду: просто все лежит пока не поднимут руками. А если нагрузка возрастает, отдувается по-прежнему одна-единственная нода.

Осознав масштаб катастрофы, Алексей принял стратегическое решение: перенести Redis в Kubernetes, развернутый поверх новых выделенных серверных мощностей.
План звучит надежно как швейцарские часы: отказоустойчивость повысится и управляемость сервисом улучшится, процессы автоматизируются, а главное, наконец появится единый подход к настройке. Надо только немного поднапрячься.
Глава 2. Через тернии к Kubernetes
Чтобы Redis заколосился в нашем кубере, для начала, потребуется поработать с выбором инструментария для задач. А задачи следующие:
1. Управление состояниями
Есть два основных способа управлять приложениями в Kubernetes: это использовать контроллеры рабочих нагрузок StatefulSet или Deployment. Deployments предназначены для приложений, которые не сохраняют состояния. А это значит, что если случится отказ мастер-ноды или нужно будет обработать больше запросов, все закешированные данные слетят при переключении. Оно нам надо? Поэтому использовать будем StatefulSet — этот контроллер обеспечивает постоянство состояния, порядковую зависимость и стабильность имен подов. Это гарантирует, что поды Redis будут иметь стабильные сетевые идентификаторы и будут использовать постоянные хранилища (Persistent Volumes) для хранения данных, а значит никакая важная инфа не потеряется.
2.Автоматический фейловер
Это, собственно, чтобы было что и куда переключать в случае отказа. Способов реализации фейловера тоже масса. Но поскольку мы имеем дело с унаследованным решением, которое вообще не подразумевалось как кластерное изначально, и нам важна гибкость, но пока не нужны извращения типа шардирования данных, возьмем самый распространенный инструмент: Sentinel. Он осуществляет мониторинг мастера Redis и его реплик, и в случае сбоя мастера автоматически переключается на одну из реплик, назначая ее новым мастером. Это обеспечивает высокую доступность Redis, минимизируя время простоев и ручную интервенцию при отказах, что особенно важно для критически важных приложений, зависящих от данных в реальном времени.
3.Упрощение развертывания и обновления
Развертывание сложных приложений в Kubernetes — процесс витиеватый и подверженный ошибкам, если нет опыта с кубером. Шаг влево, шаг вправо — и вот уже всё работает не так, как должно. Чтобы избежать неразберихи и иметь возможность в случае чего разлепить все пельмени обратно, Алексей решил использовать helm-чарты (хотя это далеко не единственный способ решить вопрос). Helm-чарты — это такие шаблоны, которые пошагово описывают, как развернуть, настроить и управлять приложением. Как та самая инструкция из Ikea, где обозначено какие болтики куда вкручиваются и в каком порядке. Поскольку задачи разработчиков часто универсальны, есть целые компании, которые специализируются на создании таких шаблонов. Одна из них — Bitnami, их helm-чарт для Redis славится своей простотой и надежностью, вот его и возьмем, чтобы минимизировать количество ручной работы и сократить вероятность ошибок. На всякий случай в пакете Bitnami есть не только Redis, но и все известные СУБД и многое другое — небольшой бонус для использования единого helm-чарта в компании Алексея.
4.Балансировка трафика
Логично, что если Redis падает от нагрузки, ее нужно сбалансировать и распределить между несколькими серверами или инстансами. Один из самых популярных способов это сделать, использовать HAProxy. Он может обрабатывать большое количество соединений и высокую нагрузку с низкой задержкой, оставляет пространство для маневра с конфигурациями, а еще он бесплатный и неплохо поддерживается сообществом энтузиастов, берем, тем более Алексей его уже настраивал.
…И вот Алексей вооружен, осталось только вызывать кластер на бой победить. Что может пойти не так?

Глава 3. Битва при кластеровой пади
Алексей был опытным DevOps’ом и знал, что с первого раза ничего не получается, но то, что на бумаге выглядело как маленькая и победоносная война с кластером, на деле пошло не так абсолютно по каждому пункту. Выяснилось, что:
StatefulSet не находит Persistent Volumes. При сбросе пода данные удаляются.
Sentinel не видит реплики Redis.
Процесс переключения с мастер-ноды, почему-то происходит неприлично долго.
Когда поды перезапускаются, они получают новые IP-адреса, что создает проблемы с обращением к ним и делает всю конфигурацию Redis недействительной и превращает все данные в тыкву.
На этом месте глаз нашего героя уже начал подергиваться. Но он не сдался и, закатав рукава, погрузился в решение проблем.
При ближайшем рассмотрении оказалось, что в проблеме с Persistent Volumes виновны старые железки. StatefulSet просто не мог найти или присоединить постоянное хранилище на выделенных серверах 2015 года выпуска, попытка присоединить к ним локальный диск раз за разом терпела фиаско. «Раз не пашет с физическим, попробуем виртуальное», — решил наш герой и хладнокровно настроил Storage Class для динамического проксируемого хранения (NFS/iSCSI). Фокус сработал и StatefulSet наконец-то разглядел хранилища.
Проблема с Sentinel и репликами Redis тоже решилась довольно просто: достаточно было покопаться в Network Policies, открыть порт 26379 и сразу все стало видно, удобно, понятно.
Над тем, почему ноды переключаются так долго, пришлось все-таки поломать голову: машины как будто решили устроить заговор за спиной людей и байкотировали своевременное переключение. Запустив руки по локоть в конфиг, Алексей обнаружил, что виновата здесь отнюдь не тайная ложа, а явная лажа. Всего один лишний нолик после запятой в строке down-after-milliseconds или failover-timeout способен привести к катастрофическому времени простоя и потере данных. Предательская цифра была выловлена и выкорчевана.
Но файнал-боссом в битве оказалась проблема с локальными адресами. Есть у подов в Kubernetes такая особенность: когда они создаются и запускаются, они получают уникальные IP-адреса, которые используются другими компонентами или подами для установки соединения с ними. Но каждый перезапуск все обнуляет и адреса назначаются заново. Получается, если приложение или сервис нуждается в жестко прописанных IP для соединения с подами, изменение адресов потребует обновления всей конфигурации, иначе при попытке обратиться к старому IP будет сбой.
Если мы не можем быть уверены в том, что конкретный IP будет принадлежать интересующей нас поде, нужно:
либо придумать способ, как логически объединить поды, чтобы даже в случае неразберихи интересующий нас адресат нашелся, невзирая на смену IP;
либо придумать систему учета, которая будет сопоставлять конкретного адресата с IP, девопсеры называют это affinity rules.
Второй вариант представляется как-то понадежнее. Алексей подумал о том, что если настроить жесткие правила определения IP внутри конфигурации StatefulSet через доменное имя, то при падении любой ноды конфигурация всегда будет указывать на то, какой ноде какое доменное имя соответствует. И при восстановлении ноды это правило также будет работать.Но чтобы создать такую систему, нам нужно будет использовать вместо IP комбинацию FQDN, которые будут содержать полные, уникальные и постоянные доменные имена, и аффинити-рулов — правил размещения подов на нодах кластера.
Итак, после долгих упражнений по составлению манифестов, настройки и проверки работоспособности DNS, тестирования, исправления и оптимизации наш герой Алексей-таки победил кластер, и Redis в нем начал работать корректно.
Глава 4. Победа и ее цена
Кричали тимлиды УРА и в поды конфиги бросали! Алексей был на коне, еще бы: сбои снизились на 70%, время деплоя сократилось с 4 часов до 5 минут и все это за 2 недели, притом, что другие рутинные задачи-то никто не отменял. Босс даже похвалил и пообещал повышение и новый монитор. Успех? Да. Молодец ли Алексей? Несомненно. Но что приходит вместе с таким замечательным результатом?

А приходит помимо соответствия Cloud Native стандартам целый зоопарк компонентов, который хоть и изредка нужно обновлять и поддерживать:
обновления Kubernetes;
патчи Redis и Sentinel;
версии Helm-чартов;
конфигурации HAProxy.
А кто теперь будет это поддерживать? Разумеется, Алексей. Теперь, давайте представим, что произойдет, если наш герой заболеет, уйдет в отпуск, согласится на более выгодное предложение у конкурентов или вообще выгорит и уедет заниматься кайтсерфингом во Вьетнам? Скорее всего компанию ждет эпопея с поиском замены, долгим выяснением как же это все работает, кто должен этим заниматься, не нужен ли новый сотрудник на поддержку этого решения… и история повторится, как с тем разработчиком, который в начале написал скрипты, а спустя несколько лет ушел в закат, а мы продолжим кататься на этой карусели бессмысленного и беспощадного легаси. Итогом станут проблемы с продуктами и сервисами, репутационные риски, упущенные прибыли.
Эпилог. Альтернативы
Критиковать DevOps’a мы не будем, потому что он без шуток молодец и проделал титаническую работу. Давайте беспристрастно посмотрим, что можно было сделать иначе, но с тем же результатом, быстрее и без необходимости поддерживать компоненты, на которых все держится?
Можно было свернуть на дорожку управляемых сервисов еще на этапе, где выяснилось, что Redis-то не масштабируемый. Например, вот так выглядело бы приключение Алексея, если бы он изначально выбрал Redis в PaaS-формате:
4 клика в UI или Terraform-скрипт — и кластер готов.
Автоматический failover без ручной настройки Sentinel.
Встроенный мониторинг (Prometheus + Grafana) и логи.
Безопасность: TLS, RBAC, регулярные обновления.

И никаких апдейтов, бэкапов или масштабирования вручную, оплата только за использование (OPEX вместо CAPEX) и SLA 99.95% из коробки. Если ваша рука уже потянулась написать в комментариях «Реклама. Дизлайк, отписка», подождите: смысл этой истории был отнюдь не в том, чтобы обесценить мастерство DevOps’ов и склонить всех пользоваться нашим сервисом. Ну то есть пользуйтесь, конечно, мы только за, просто при выборе любого решения, будь то self-hosted Redis или managed-service в облаке, убедитесь, что учли все факторы и до конца осознаете, чем вы готовы пожертвовать на длинной дистанции, а чем нет.
Вот в каких ситуациях Redis в облаке не подойдет:
1. Когда нужен абсолютный контроль над всем.
Несмотря на то, что облако предоставляет достаточно свободы, бывают случаи, когда нужны кастомные конфигурации, например нестандартные версии Redis, модификации ядра, Lua-скрипты и т.д. Конкретно мне такие случаи почти не встречались, гораздо чаще бывают истории, где требуется гибкость в настройке репликации, шардинга или политик сохранения данных — эти задачи частично перекрываются Managed Redis, но не во всех случаях, нужно смотреть на каждый конкретный.
2. При жестких комплаенс-политиках и требованиях безопасности
Есть организации, которым в принципе нельзя выносить данные в публичное облако, например, банки. Бывает, что внутренние стандарты безопасности требуют изоляции на уровне железа. Обычно это означает, что дорожка в облака закрыта в принципе, но не всегда — возможны, например, вариации частного облака.
3. Если команда и инфраструктура полностью готовы к поддержке
Если в штате есть опытные DevOps/Kubernetes-инженеры, нет дефицита оборудования и достаточно ресурсов для мониторинга, бэкапов и обновлений, то почему бы и не хостить самостоятельно? Как показала история нашего героя, это возможно делать успешно.
4. При специфических нагрузках
Все-таки дата-центры облачных провайдеров, какими бы классными и быстрыми ни были их сети, не могут гарантировать полное отсутствие задержек, хотя бы в силу удаленности от пользователя. А в каком-нибудь трейдинге или профессиональном киберспрорте это может быть критично. Еще бывают варианты, когда в компании уже используются какие-то другие self-hosted инструменты, например, внутренние очереди Kafka, и Redis нужно интегрировать с ними.
Ну а если в приоритете скорость и простота развертывания, ресурсов на поддержку нет, а надежность требуется высокая, лучше все-таки смотреть в сторону Managed Redis в облаке. В комментах можно поспорить с этим утверждением, рассказать историю своих отношений с Redis или поделиться своими примерами, как приключение на 20 минут превратилось в Санта-Барбару.