Привет! Меня зовут Сергей Тетерюков, и я работаю инженером инфраструктуры и автоматизации в X5 Tech. Недавно я написал для коллег обзорную статью о БД Apache Cassandra DB и её деплое, и теперь хочу поделиться ей с вами.
Краткое и поверхностное описание Cassandra DB
Apache Cassandra — высокопроизводительная, отказоустойчивая распределённая не реляционная (NoSQL) БД, позволяющая создать надёжное масштабируемое хранилище данных.
Cassandra использует язык CQL, синтаксис которого аналогичен SQL.
Компоненты cassandra db:
Нода(Node) или узел – основной элемент хранилища cassandra db.
-
Дата-центр (Data-center) – состоит из одной или нескольких нод, связанных между собой. Может быть физическим или виртуальным.
Деление на дата-центры позволяет распределять рабочую нагрузку, обеспечивает те самые reliability и fault tolerance.
В каждый дата-центр обычно входят несколько нод, как минимум одна из которых выполняет роль раздающей (Seed); рекомендуется делать более одной seed-ноды на один дата-центр.
Кластер (Cluster) – объединение дата-центров.
Для более детального изучения архитектуры и того, как работает Cassandra рекомендую ознакомиться с основными разделами из официальной документации:
Рассмотрим практическую продуктовую задачу
Задеплоить кластер Apache Cassandra DB в нескольких окружениях (например, dev, stage, и prod).
Установить компоненты, необходимые для мониторинга Cassandra DB (экспортеры для сбора метрик состояния и ресурсов серверов, на которых развернуты ноды БД кассандры, и непосредственно метрик самой БД).
Варианты реализации деплоя
-
В докер-контейнерах.
Плюсы:
— Есть готовые докер-образы, как для Cassandra DB, так и для node-exporter, cassandra-exporter => минимум настроек.Минусы (выявленные методом проб и ошибок на личном опыте):
— Требуется подготовка хостов — установка и настройка docker и сопутствующих пакетов.
— Для более гибкой настройки потребуется модификация образов.
— Для успешного объединения отдельных нод Cassandra DB в кластер могут потребоваться дополнительные танцы с бубном: так, например, у меня при одновременном запуске контейнеров ноды как минимум одна из нод не могла корректно получить токены, и не проходила проверку для получения статуса UN (Up, Normal). Пришлось настроить ожидание с запуском каждого следующего контейнера только после завершения запуска и инициализации предыдущего, начиная с seed-ноды.
— Сложности с настройками контейнеров экспортеров (например, node-exporter официально не рекомендуется деплоить в докер-контейнере, т.к. для корректной работы ему требуется доступ к системе хоста).
— Сложности взаимодействия контейнеров БД и экспортеров между собой. У меня, например, так и не удалось подружить контейнер cassandra-exporter с контейнером самой cassandra-db. -
В k8s-кластере.
Плюсы:
— Как и в случае с docker: готовые докер-образы всех компонентов.
— Возможности настроить автомасштабирование.
— Высокая доступность и отказоустойчивость.
— GAP-стек хорошо адаптирован для мониторинга kubernetes, в т.ч. есть готовые решения в виде helm-chart'ов.Минусы:
— Дополнительные сложности, связанные с реализацией деплоя stateful-приложения.
— Для более гибкой настройки потребуется модификация образов.
— Необходимо будет решать проблемы с объединением cassandra-узлов в единый cassandra-кластер и их идентификацией (для которой требуются ip-адреса узлов).
— Масса сложностей (согласно информации с просторов сети) с настройками cassandra-exporter'а, чтобы он мог корректно забирать метрики.В сети можно найти несколько готовых helm-chart'ов для cassandra-db, например: один, второй (более не поддерживается разработчиками). Также есть несколько kubernetes-операторов разной степени готовности и поддержки, со своими плюсами и минусами.
-
Установка пакетов на хостах.
Плюсы:
— Отсутствие проблем взаимодействия компонентов между собой, описанных для реализации в докер-контейнерах.
— Возможность более гибкой настройки компонентов.Минусы:
—Требуются дополнительные настройки для организации работы cassandra как сервиса с автозапуском при старте хоста.
Для production среды рекомендуется деплой БД на хостах, этот вариант и будет рассмотрен далее.
В качестве основного инструмента для деплоя был выбран ansible. Соответственно для каждого компонента (БД и 2 экспортера) у нас будет своя ansible-role.
Общие особенности реализации процесса:
ansible.cfg:
[defaults]
host_key_checking = False
deprecation_warnings = False
interpreter_python = /usr/bin/python3
vault_password_file = vault_secret.sh
stdout_callback = yaml
Пара пояснений:
[stdout_callback = yaml] — вывод при исполнении роли становится более читаемым;
[vault_password_file = vault_secret.sh] — данный параметр позволит хранить пароль от ansible-vault в качестве переменной окружения и не передавать его при запуске роли.
В скрипт-файле vault_secret.sh:
#!/bin/bash
echo $ANSIBLE_VAULT_PASS
Организация директории group_vars:
group_vars
├── all
│ └── var.yml
├── dev
│ ├── var.yml
│ └── vault.yml
├── prod
│ ├── var.yml
│ └── vault.yml
└── stage
├── var.yml
└── vault.yml
Все секреты храним в group_vars/<environment>/vault.yml, шифруемые c помощью ansible-vault.
Например, пароль от учётной записи, под которой будет осуществляться ssh-подключение к хостам — в group_vars/all/var.yml:
ansible_password: "{{ vault_ansible_password }}"
В group_vars/<environment>/vault.yml:
ansible__vault_password: "MySecr3tP@s$w0rd!"
И шифруем:
$ ansible-vault encrypt group_vars/test/vault.yml
После чего файл с паролем выглядит примерно вот так:
$ANSIBLE_VAULT;1.1;AES256
64613635303233376461643834326339343535303763353731643762383136656566666336646636
3866666162383666656137346365663631333532633762610a373733353863666230383630643661
61613262366138373537663866303434643565313262366133383732313062393531636431653862
3263653434313938320a386638653734376330646136303231323966393837333934373764306230
32306266366237393137303961633232666636643062306264633633623930666339326637643835
61396237386263613761626439303831313939366365303431616432656563366261633065636261
32623861653736386266343866386135373866636632316533363531356561643833326636303632
65613230323333383364323938376539303432656538353564383735373237306231366561383637
6338
Файл hosts(inventory):
[seeds]
csnd1 ansible_host=10.10.10.10
csnd2 ansible_host=10.10.10.11
[workers]
csnd3 ansible_host=10.10.10.12
[prod:children]
seeds
workers
[dc1:children]
seeds
workers
[all:vars]
ansible_connection=ssh
ansible_become=yes
ansible_become_user=root
Объявление группы 'seeds' необходимо для дальнейшего определения seed-нод кластера.
Формирование остальных групп — в соответствии с требуемой в вашей задаче реализацией.
Далее рассмотрим роли для деплоя каждого компонента
-
Ansible-role для Cassandra DB:
В качестве основы была выбрана готовая роль от LoCP (League of Crafty Programmers Ltd). Было изучено несколько вариантов, но роль LoCP, на мой субъективный взгляд, наиболее понятная и подходящая.
Структура роли:
roles/cassandra-db
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
├── tasks
│ ├── assertions.yml
│ ├── configure.yml
│ ├── hotfixes.yml
│ ├── install
│ │ ├── Debian.yml
│ │ └── RedHat.yml
│ ├── join_cluster.yml
│ ├── main.yml
│ ├── service.yml
│ └── variables.yml
├── templates
│ ├── cassandra.yaml.j2
│ └── etc
│ └── systemd
│ └── system
│ └── cassandra.service.j2
└── vars
├── Debian.yml
├── RedHat.yml
└── main.yml
В целом роль самодостаточная:
Позволяет развернуть кластер cassandra как на серверах семейства RedHat, так и Debian.
Содержит шаги, необходимые для добавления cassandra в систему в качестве сервиса.
Есть таски для добавления новых нод в кластер.
Пояснения и советы:
Для корректного создания основного конфигурационного файла cassandra — cassandra.yaml — не забудьте добавить в defaults/main.yml либо в yaml-конфиг, вызывающий роль (по аналогии с примером из README.md данной роли), параметры cassandra_configuration и cassandra_regex_replacements:
cassandra_datacenter1_name: "dc1"
cassandra_rack_name: "rack1"
seeds: "{{ seeds_list | join(',') }}"
cassandra_regex_replacements:
- path: cassandra-env.sh
line: MAX_HEAP_SIZE="{{ cassandra_max_heapsize_mb }}M"
regexp: ^#MAX_HEAP_SIZE="4G"
- path: cassandra-env.sh
line: HEAP_NEWSIZE="{{ cassandra_heap_new_size_mb }}M"
regexp: ^#HEAP_NEWSIZE="800M"
- path: cassandra-rackdc.properties
line: 'dc={{ cassandra_datacenter1_name }}'
regexp: '^dc='
- path: cassandra-rackdc.properties
line: 'rack={{ cassandra_rack_name }}'
regexp: '^rack='
сassandra_configuration:
authenticator: PasswordAuthenticator
cluster_name: "{{ envname }}-cluster"
commitlog_directory: /data/cassandra/commitlog
commitlog_sync: periodic
commitlog_sync_period_in_ms: 10000
data_file_directories:
- /data/cassandra/data
endpoint_snitch: GossipingPropertyFileSnitch
hints_directory: "/data/cassandra/hints"
listen_address: "{{ ansible_default_ipv4.address }}"
rpc_address: "{{ ansible_default_ipv4.address }}"
partitioner: org.apache.cassandra.dht.Murmur3Partitioner
num_tokens: 256
saved_caches_directory: /data/cassandra/saved_caches
seed_provider:
- class_name: "org.apache.cassandra.locator.SimpleSeedProvider"
parameters:
- seeds: "{{ seeds }}"
start_native_transport: true
Значения cassandra_max_heapsize_mb и cassandra_heap_new_size_mb рассчитываются автоматически (на основе фактических технических параметров серверов) в тасках, описанных в tasks/variables.yml
seeds_list определяется следующей конструкцией в group_vars/all.yml:
seeds_list: "{{ groups['seeds'] | map('extract',hostvars,'ansible_host') | list }}"
Соответственно в hosts(inventory) должна быть группа seeds, которые и будут определены в качестве сидов кластера.
-
В качестве бонуса, если вдруг в вашей задаче требуется первоначальная конфигурация БД, например, добавить пространство ключей (keyspace) и пользователя (user):
а. Для начала надо дождаться, что кластер поднялся и слушает порты для подключения к нему (9042) и взаимодействия нод между собой (7000). Для этого можно добавить следующую таску после всех задач конфигурации и запуска:
- name: Wait for the Cassandra Listener
wait_for:
timeout: 120
host: "{{ ansible_default_ipv4.address }}"
port: "{{ item }}"
loop:
- 9042
- 7000
b. В templates/ добавить шаблон cqlsh-скрипта, например, в моём случае было достаточно такого:
ALTER KEYSPACE system_auth
WITH replication = {
'class': 'NetworkTopologyStrategy',
'{{ cassandra_datacenter1_name }}': {{ cass_dc1_nodes_number }}
};
CREATE ROLE IF NOT EXISTS {{ cassandra_user }}
WITH PASSWORD = '{{ cassandra_user_password }}'
AND LOGIN = true;
CREATE KEYSPACE IF NOT EXISTS {{ cassandra_keyspace }}
WITH replication = {
'class': 'NetworkTopologyStrategy',
'{{ cassandra_datacenter1_name }}': {{ cass_dc1_nodes_number }}
};
Не забыть объявить необходимые переменные:
cassandra_user: "test_user"
cassandra_keyspace: "test_keyspace"
cassandra_datacenter1_name: "dc1"
cassandra_rack_name: "rack1"
cass_dc1_nodes_number: "{{ groups['dc1'] | length }}"
cqlsh_script_template: "cqlsh_script.txt.j2"
cqlsh_script_path: "/root/cqlsh_script.txt"
cassandra_user_password — добавить в ansible-vault
c. Скопировать скрипт на одну ноду кластера, выполнить его и после удалить:
- block:
- name: Apply cqlsh_script
template:
src: "{{ cqlsh_script_template }}"
dest: "{{ cqlsh_script_path }}"
owner: root
group: root
mode: '0644'
- name: add DB data (keyspace, user)
shell: "cqlsh {{ ansible_default_ipv4.address }} -u cassandra -p cassandra -f {{ cqlsh_script_path }}"
- name: Remove cqlsh_script file
file:
path: "{{ cqlsh_script_path }}"
state: absent
when: inventory_hostname == groups['seeds'][0]
-
Ansible-role для node-exporter'а:
Основа — готовая роль под авторством cloudalchemy. Структура роли:
roles/node-exporter
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
├── tasks
│ ├── configure.yml
│ ├── install.yml
│ ├── main.yml
│ ├── preflight.yml
│ └── selinux.yml
├── templates
│ ├── config.yaml.j2
│ └── node_exporter.service.j2
└── vars
└── main.yml
Необходимости в модификации данной роли почти нет (за исключением моментов, связанных с особенностями инфраструктуры (например, доступность внешних репозиториев из внутренней сети вашей компании), например, node_exporter_repo_url), собственная документация роли, на мой взгляд исчерпывающая.
Пояснения и советы:
Роль адаптирована под дистрибутивы семейств RHEL, Fedora, ClearLinux.
Node-exporter будет функционировать в качестве systemd service unit.
-
Предусмотрены два варианта установки экспортера:
a. Бинарник скачивается из репозитория, адрес которого передаётся в переменной node_exporter_repo_url. В случае, если ваше окружение имеет ограничения в доступе к официальному репозиторию, следует заменить его на адрес своего, доступного репозитория.
b. Бинарник заранее помещается в директорию с файлами роли (roles/node-exporter/files), либо по другому удобному вам пути, который далее передаётся в переменной node_exporter_binary_local_dir
Выбирайте более подходящий для вас вариант, но не передавайте одновременно значения и в node_exporter_repo_url, и в node_exporter_binary_local_dir, т.к. они отвечают за взаимозаменяемые сценарии.
Для изменения версии экспортера — поменять значение параметра node_exporter_version в defaults/main.yml. Здесь же найдутся параметры для более гибкой настройки конфигурации (config.yaml) — параметры:
node_exporter_tls_server_config: {}
node_exporter_http_server_config: {}
node_exporter_basic_auth_users: {}
При необходимости внести кастомные изменения в конфигурационные файлы настройки экспортера (config.yaml) и настройки сервиса (node_exporter.service): шаблоны файлов расположены в каталоге templates/.
Если по каким-то причинам вам потребуется поменять порт, на котором будет работать экспортер — меняйте переменную node_exporter_web_listen_address (стандартное значение "0.0.0.0:9100"). Аналогично с эндпоинтом для метрик — переменная node_exporter_web_telemetry_path (стандартное значение "/metrics").
3.Ansible-role для cassandra-exporter'а:
В качестве основы была выбрана роль от criteo. Струтура:
roles/cassandra-exporter
├── README.md
├── defaults
│ └── main.yml
├── files
│ └── config.yml
├── handlers
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
├── cassandra_exporter.sh.j2
└── etc
└── systemd
└── system
└── prometheus-cassandra-exporter.service.j2
Изначально эта роль является развитием оригинального JMX exporter, но доработана для более лёгкой интеграции с Cassandra DB. По сути, установки экспортера с помощью этой роли достаточно, чтобы после настройки прометеуса на забор метрик с этого экспортера и установки в графане этого дашборда получить исчерпывающий набор информативных графиков для мониторинга нашего кассандра-кластера в разрезе работы БД.
Также важно отметить, что в рамках этой роли экпортер устанавливается в формате standalone, т.е. как отдельный компонент. Если у вас на проекте предполагается высокая нагрузка на cassandra DB, что закономерно повлечёт за собой увеличение количества метрик, то возможно вам стоит рассмотреть установку cassandra-exporter как java-agent (что позволит ускорить сбор метрик и снизить нагрузку на систему; из минусов — необходимость встраивать агент в конфигурацию cassandra DB, и, как следствие, потенциальные проблемы с совместимостью); в этом случае советую ознакомиться со следующей реализацией экспортера.
Пояснения и советы:
cassandra-exporter будет функционировать в качестве systemd service unit.
Версия экспортера задаётся в defaults/main.yml в переменной cassandra_exporter_version. Актуальную версию можно посмотреть здесь.
Порт, на котором будет висеть экспортер задаётся в defaults/main.yml в переменной cassandra_exporter_listen_port (значение по умолчанию в роли — 8080, но вообще стандартный порт для cassandra-exporter — 9500). Этот же порт следует прописывать в конфиге прометеуса для стягивания метрик.
Бинарник и конфиг-файл скачиваются из репозитория, адреса передаются в переменных cassandra_exporter_binary_url и cassandra_exporter_config_url. В случае, если ваша инфраструктура имеет ограничения в доступе к официальному репозиторию, следует заменить значения переменных на адреса своего, доступного репозитория.
Если вам потребуется, чтобы установочный бинарник и/или файл конфигурации сразу были в проекте, и не нужно было их скачивать в процессе отработки роли, то потребуются следующие доработки роли:
— Закинуть эти файлы (по аналогии с node-exporter'ом,) в директорию с файлами роли (roles/cassandra-exporter/files) либо в другую директорию проекта.
— Добавить переменные cassandra_exporter_binary, cassandra_exporter_config, в которых будут указаны пути до файлов в проекте (если файлы будут помещены в roles/cassandra-exporter/files, то в значениях переменных достаточно будет указать только имена этих файлов, например: 'cassandra_exporter.jar', 'config.yml').
— Добавить следующие исполнительные таски:
- name: 'copy cassandra exporter binary'
copy:
src: '{{ cassandra_exporter_binary }}'
dest: '{{ cassandra_exporter_dist_dir }}/cassandra.jar'
owner: '{{ cassandra_exporter_user }}'
group: '{{ cassandra_exporter_group }}'
mode: '0644'
when: cassandra_exporter_binary is defined
А в таске 'download cassandra exporter binary' добавить условие выполнения:
when: cassandra_exporter_binary is not defined
- name: 'сopy cassandra exporter config'
copy:
src: '{{ cassandra_exporter_config }}'
dest: '{{ cassandra_exporter_config_dir }}/config.yml'
owner: '{{ cassandra_exporter_user }}'
group: '{{ cassandra_exporter_group }}'
mode: '0644'
when: cassandra_exporter_config is defined
Переменные cassandra_exporter_dist_dir и cassandra_exporter_config_dir - уже заданы в роли.
Конфиг-файл экспортера советую именно таким образом держать в проекте, чтобы при необходимости иметь возможность вносить доработки/изменения через код.
Итог
Имеем набор ролей, который позволит тремя командами:
$ ansible-playbook -i ansible/hosts ansible/install-cassandra-db.yml
$ ansible-playbook -i ansible/hosts ansible/install-cassandra-exporter.yml
$ ansible-playbook -i ansible/hosts ansible/install-node-exporter.yml
Развернуть полный набор — кластер БД cassandra и его мониторинг как на уровне ресурсов системы, так и на уровне самой БД. Все три компонента установятся на хостах в качестве systemd service unit'ов, перезапускаемых при старте системы, так что в случае перезагрузки хостов, не потребуется ручного запуска.
При установке желательно придерживаться последовательности:
cassandra-db
cassandra-exporter — в этом случае cassandra-exporter сразу найдёт запущенный сервис; node-exporter от остальных компонентов не зависит, и его можно устанавливать в любой момент.
В заключение, для проверки работы каждого компонента:
-
БД должна быть доступна при обращении к любой из нод кластера на порт 9042.
— Для удалённого подключения через консоль нам понадобится клиент cqlsh:
$ cqlsh <cassandra_node1_ip-address>:9042 -u my_cass_user
Более подробно о работе с клиентом cqlsh.
— Для диагностики кластера при локальном подключении к хостам-нодам cassandra можно использовать инструмент — nodetool, устанавливается автоматически вместе с cassandra-db. Более подробно о работе с nodetool.
Метрики node-exporter должны быть доступны при обращении к каждому хосту-ноде кластера на порт 9100,
Метрики cassandra-exporter — на порт 9500.