Миграция Cassandra в Kubernetes: особенности и решения

Миграция Cassandra в Kubernetes: особенности и решения

С базой данных Apache Cassandra и необходимостью её эксплуатации в рамках инфраструктуры на базе Kubernetes мы сталкиваемся регулярно. В этом материале поделимся своим видением необходимых шагов, критериев и существующих решений (включая обзор операторов) для миграции Cassandra в K8s.

«Кто может управлять женщиной, справится и с государством»

Кто же такая Cassandra? Это распределенная система хранения, предназначенная для управления большими объемами данных, при этом обеспечивающая высокую доступность без единой точки отказа. Проект вряд ли нуждается в длинном представлении, поэтому приведу лишь основные особенности Cassandra, что будут актуальны в разрезе конкретной статьи:

  • Cassandra написана на Java.
  • Топология Cassandra включает несколько уровней:
    • Node — один развернутый экземпляр Cassandra;
    • Rack — группа экземпляров Cassandra, объединенных по какому-либо признаку, находящаяся в одном дата-центре;
    • Datacenter — совокупность всех групп экземпляров Cassandra, находящихся в одном дата-центре;
    • Cluster — совокупность всех дата-центров.
  • Для идентификации узла Cassandra использует IP-адрес.
  • Для быстроты операций записи и чтения часть данных Cassandra хранит в оперативной памяти.

Теперь — к собственно потенциальному переезду в Kubernetes.

Check-list для переноса

Говоря о миграции Cassandra в Kubernetes, мы надеемся, что с переездом управлять ей станет удобнее. Что для этого потребуется, что в этом поможет?

1. Хранилище для данных

Как уже уточнялось, часть данных Cassanda хранит в оперативной памяти — в Memtable. Но есть и другая часть данных, которая сохраняется на диск, — в виде SSTable. К этим данным добавляется сущность Commit Log — записи обо всех транзакциях, которые тоже сохраняются на диск.

Миграция Cassandra в Kubernetes: особенности и решения
Схема транзакций записи в Cassandra

В Kubernetes мы можем использовать для хранения данных PersistentVolume. Благодаря отработанным механизмам, работать с данными в Kubernetes с каждым годом становится всё проще.

Миграция Cassandra в Kubernetes: особенности и решения
Каждому pod’у с Cassandra мы выделим свой PersistentVolume

Важно отметить, что Cassandra сама по себе подразумевает репликацию данных, предлагая для этого встроенные механизмы. Поэтому, если вы собираете кластер Cassandra из большого числа узлов, то нет необходимости использовать для хранения данных распределенные системы вроде Ceph или GlusterFS. В этом случае логично будет хранить данные на диске узла при помощи локальных персистентных дисков или монтирования hostPath.

Другой вопрос, если вы хотите создавать для каждой feature-ветки отдельное окружение для разработчиков. В этом случае правильным подходом будет поднимать один узел Cassandra, а данные хранить в распределенном хранилище, т.е. упомянутые Ceph и GlusterFS станут вашей опцией. Тогда разработчик будет уверен, что не потеряет тестовые данные даже при потере одного из узлов Kuberntes-кластера.

2. Мониторинг

Практически безальтернативным выбором для реализации мониторинга в Kubernetes является Prometheus (подробно мы об этом рассказывали в соответствующем докладе). Как дела у Cassandra с экспортерами метрик для Prometheus? И, что в чем-то даже главнее, с подходящими к ним dashboard’ами для Grafana?

Миграция Cassandra в Kubernetes: особенности и решения
Пример внешнего вида графиков в Grafana для Cassandra

Экспортеров всего два: jmx_exporter и cassandra_exporter.

Мы выбрали для себя первый, потому что:

  1. JMX Exporter растет и развивается, в то время как Cassandra Exporter не смог получить должной поддержки сообщества. Cassandra Exporter до сих пор не поддерживает большинство версий Cassandra.
  2. Можно запустить его как javaagent при помощи добавления флага -javaagent:<plugin-dir-name>/cassandra-exporter.jar=--listen=:9180.
  3. Для него есть адекватный dashboad, который несовместим с Cassandra Exporter.

3. Выбор примитивов Kubernetes

Согласно вышеизложенной структуре кластера Cassandra, попробуем перевести всё, что там описано, в терминологию Kubernetes:

  • Cassandra Node → Pod
  • Cassandra Rack → StatefulSet
  • Cassandra Datacenter → пул из StatefulSets
  • Cassandra Cluster → ???

Получается, что не хватает какой-то дополнительной сущности, чтобы управлять всем кластером Cassandra сразу. Но если чего-то нет, мы можем это создать! В Kubernetes для этого предназначен механизм определения собственных ресурсов — Custom Resource Definitions.

Миграция Cassandra в Kubernetes: особенности и решения
Объявление дополнительных ресурсов для логов и оповещений

Но сам по себе Custom Resource ничего не значит: ведь для него нужен контроллер. Возможно, придется прибегнуть к помощи Kubernetes-оператора

4. Идентификациия pod’ов

Пунктом выше мы согласились, что один узел Cassandra будет равняться одному pod’у в Kubernetes. Но IP-адреса у pod’ов каждый раз будут разными. А идентификация узла в Cassandra происходит именно на основе IP-адреса… Получается, что после каждого удаления pod’а кластер Cassandra будет добавлять в себя новый узел.

Выход есть, и даже не один:

  1. Мы можем вести учет по идентификаторам хостов (UUID’ам, однозначно идентифицирующим экземпляры Cassandra) или по IP-адресам и сохранять это всё в каких-то структурах/таблицах. У метода два основных недостатка:
    • Риск возникновения условия гонки при падении сразу двух узлов. После поднятия узлы Cassandra одновременно пойдут запрашивать для себя IP-адрес из таблицы и конкурировать за один и тот же ресурс.
    • Если узел Cassandra потерял свои данные, мы больше не сможем его идентифицировать.
  2. Второе решение кажется небольшим хаком, но тем не менее: мы можем создавать Service с ClusterIP для каждого узла Cassandra. Проблемы этой реализации:
    • Если в кластере Cassandra очень много узлов, нам придется создать очень много Service’ов.
    • Возможность ClusterIP реализована через iptables. Это может стать проблемой, если в кластере Cassandra много (1000… или даже 100?) узлов. Хотя балансировка на базе IPVS способна решить эту проблему.
  3. Третье решение — использовать для узлов Cassandra сеть узлов вместо выделенной сети pod’ов при помощи включения настройки hostNetwork: true. Данный метод накладывает определённые ограничения:
    • На замену узлов. Нужно, чтобы новый узел обязательно имел тот же IP-адрес, что и предыдущий (в облаках вроде AWS, GCP это сделать практически невозможно);
    • Используя сеть узлов кластера, мы начинаем конкурировать за сетевые ресурсы. Следовательно, выложить на один узел кластера более одного pod’а с Cassandra будет проблематично.

5. Бэкапы

Мы хотим сохранять полную версию данных одного узла Cassandra по расписанию. Kubernetes предоставляет удобную возможность с использованием CronJob, но тут палки в колеса нам вставляет сама Cassandra.

Напомню, что часть данных Cassandra хранит в памяти. Чтобы сделать полный бэкап, нужно данные из памяти (Memtables) перенести на диск (SSTables). В этот момент узел Cassandra перестает принимать соединения, полностью выключаясь из работы кластера.

После этого снимается бэкап (snapshot) и сохраняется схема (keyspace). И тут выясняется, что просто бэкап нам ничего не дает: нужно сохранить идентификаторы данных, за которые отвечал узел Cassandra, — это специальные токены.

Миграция Cassandra в Kubernetes: особенности и решения
Распределение токенов для идентификации, за какие данные отвечают узлы Cassandra

Пример скрипта для снятия бэкапа Cassandra от Google в Kubernetes можно найти по этой ссылке. Единственный момент, который скрипт не учитывает, — это сброс данных на узел перед снятием snapshot’а. То есть бэкап выполняется не для текущего состояния, а состояния чуть ранее. Но это помогает не выводить узел из работы, что видится очень логичным.

set -eu

if [[ -z "$1" ]]; then
  info "Please provide a keyspace"
  exit 1
fi

KEYSPACE="$1"

result=$(nodetool snapshot "${KEYSPACE}")

if [[ $? -ne 0 ]]; then
  echo "Error while making snapshot"
  exit 1
fi

timestamp=$(echo "$result" | awk '/Snapshot directory: / { print $3 }')

mkdir -p /tmp/backup

for path in $(find "/var/lib/cassandra/data/${KEYSPACE}" -name $timestamp); do
  table=$(echo "${path}" | awk -F "[/-]" '{print $7}')
  mkdir /tmp/backup/$table
  mv $path /tmp/backup/$table
done


tar -zcf /tmp/backup.tar.gz -C /tmp/backup .

nodetool clearsnapshot "${KEYSPACE}"

Пример bash-скрипта для снятия бэкапа с одного узла Cassandra

Готовые решения для Cassandra в Kubernetes

Что вообще сейчас используют для разворачивания Cassandra в Kubernetes и что из этого больше всего подходит под заданные требования?

1. Решения на базе StatefulSet или Helm-чартов

Использовать базовые функции StatefulSets для запуска кластера Cassandra — хороший вариант. При помощи Helm-чарта и шаблонов Go можно предоставить пользователю гибкий интерфейс для разворачивания Cassandra.

Обычно это работает нормально… пока не случится что-то неожиданное — например, выход узла из строя. Стандартные средства Kubernetes просто не могут учесть все вышеописанные особенности. Кроме того, данный подход очень ограничен в том, насколько он может быть расширен для более сложного использования: замены узлов, резервного копирования, восстановления, мониторинга и т.д.

Представители:

Оба чарта одинаково хороши, но при этом подвержены описанным выше проблемам.

2. Решения на базе Kubernetes Operator

Такие опции более интересны, потому что предоставляют широкие возможности по управлению кластером. Для проектирования оператора Cassandra, как и любой другой базы данных, хороший паттерн выглядит как Sidecar <-> Controller <-> CRD:

Миграция Cassandra в Kubernetes: особенности и решения
Схема управления узлами в правильно спроектированном операторе Cassandra

Рассмотрим существующие операторы.

1. Cassandra-operator от instaclustr

  • GitHub
  • Готовность: Alpha
  • Лицензия: Apache 2.0
  • Реализован на: Java

Это действительно очень многообещающий и активно развивающийся проект от компании, которая предлагает управляемые развертывания Cassandra. Он, как и описано выше, использует sidecar-контейнер, который принимает команды через HTTP. Написан на Java, поэтому иногда ему не хватает более продвинутой функциональности библиотеки client-go. Также оператор не поддерживает разные Racks для одного Datacenter.

Зато у оператора есть такие плюсы, как поддержка мониторинга, высокоуровневого управления кластером при помощи CRD и даже документация по снятию бэкапов.

2. Navigator от Jetstack

  • GitHub
  • Готовность: Alpha
  • Лицензия: Apache 2.0
  • Реализован на: Golang

Оператор, предназначенный для развертывания DB-as-a-Service. На данный момент поддерживает две базы данных: Elasticsearch и Cassandra. Имеет в себе такие интересные решения, как контроль доступа к базе данных через RBAC (для этого поднимается свой отдельный navigator-apiserver). Интересный проект, к которому стоило бы присмотреться, однако последний коммит был сделан полтора года назад, что явно снижает его потенциал.

3. Cassandra-operator от vgkowski

  • GitHub
  • Готовность: Alpha
  • Лицензия: Apache 2.0
  • Реализован на: Golang

Рассматривать его «всерьёз» не стали, так как последний коммит в репозиторий был больше года назад. Разработка оператора заброшена: последняя версия Kubernetes, заявленная как поддерживаемая, — это 1.9.

4. Cassandra-operator от Rook

  • GitHub
  • Готовность: Alpha
  • Лицензия: Apache 2.0
  • Реализован на: Golang

Оператор, развитие которого идет не так быстро, как хотелось бы. Имеет продуманную структуру CRD для управления кластером, решает проблему с идентификацией узлов при помощи Service с ClusterIP (тот самый «хак»)… но пока что это всё. Мониторинга и бэкапов из коробки сейчас нет (кстати, за мониторинг мы взялись сами). Интересный момент, что при помощи этого оператора можно также развернуть ScyllaDB.

NB: Данный оператор с небольшими доработками мы использовали в одном из наших проектов. Проблем в работе оператора за все время эксплуатации (~4 месяца работы) замечено не было.

5. CassKop от Orange

  • GitHub
  • Готовность: Alpha
  • Лицензия: Apache 2.0
  • Реализован на: Golang

Самый молодой оператор в списке: первый коммит был сделан 23 мая 2019 года. Уже сейчас он имеет в своем арсенале большое количество фич из нашего списка, подробнее с которыми можно ознакомиться в репозитории проекта. Оператор построен на базе популярного operator-sdk. Поддерживает мониторинг «из коробки». Главным отличием от других операторов является использование плагина CassKop, реализованного на Python и используемого для коммуникации между узлами Cassandra.

Выводы

Количество подходов и возможных вариантов переноса Cassandra в Kubernetes говорит само за себя: тема востребована.

На данном этапе пробовать что-то из вышеописанного можно на свой страх и риск: ни один из разработчиков не гарантирует 100%-ую работу своего решения в production-среде. Но уже сейчас многие продукты выглядят многообещающе, чтобы попробовать использовать их в стендах для разработки.

Думаю, в будущем эта женщина на корабле придется к месту!

P.S.

Читайте также в нашем блоге:

Источник: habr.com

Добавить комментарий