Привет! Меня зовут Сергей Тетерюков, и я работаю инженером инфраструктуры и автоматизации в 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 рекомендую ознакомиться с основными разделами из официальной документации:

Рассмотрим практическую продуктовую задачу

  1. Задеплоить кластер Apache Cassandra DB в нескольких окружениях (например, dev, stage, и prod).

  2. Установить компоненты, необходимые для мониторинга Cassandra DB (экспортеры для сбора метрик состояния и ресурсов серверов, на которых развернуты ноды БД кассандры, и непосредственно метрик самой БД).

Варианты реализации деплоя

  1. В докер-контейнерах.
    Плюсы:
    — Есть готовые докер-образы, как для Cassandra DB, так и для node-exporter, cassandra-exporter => минимум настроек.

    Минусы (выявленные методом проб и ошибок на личном опыте):
    — Требуется подготовка хостов — установка и настройка docker и сопутствующих пакетов.
    — Для более гибкой настройки потребуется модификация образов.
    — Для успешного объединения отдельных нод Cassandra DB в кластер могут потребоваться дополнительные танцы с бубном: так, например, у меня при одновременном запуске контейнеров ноды как минимум одна из нод не могла корректно получить токены, и не проходила проверку для получения статуса UN (Up, Normal). Пришлось настроить ожидание с запуском каждого следующего контейнера только после завершения запуска и инициализации предыдущего, начиная с seed-ноды.
    — Сложности с настройками контейнеров экспортеров (например, node-exporter официально не рекомендуется деплоить в докер-контейнере, т.к. для корректной работы ему требуется доступ к системе хоста).
    — Сложности взаимодействия контейнеров БД и экспортеров между собой. У меня, например, так и не удалось подружить контейнер cassandra-exporter с контейнером самой cassandra-db.

  2. В k8s-кластере.
    Плюсы:
    — Как и в случае с docker: готовые докер-образы всех компонентов.
    — Возможности настроить автомасштабирование.
    — Высокая доступность и отказоустойчивость.
    — GAP-стек хорошо адаптирован для мониторинга kubernetes, в т.ч. есть готовые решения в виде helm-chart'ов.

    Минусы:
    — Дополнительные сложности, связанные с реализацией деплоя stateful-приложения.
    — Для более гибкой настройки потребуется модификация образов.
    — Необходимо будет решать проблемы с объединением cassandra-узлов в единый cassandra-кластер и их идентификацией (для которой требуются ip-адреса узлов).
    — Масса сложностей (согласно информации с просторов сети) с настройками cassandra-exporter'а, чтобы он мог корректно забирать метрики.

    В сети можно найти несколько готовых helm-chart'ов для cassandra-db, например: один, второй (более не поддерживается разработчиками). Также есть несколько kubernetes-операторов разной степени готовности и поддержки, со своими плюсами и минусами.

  3. Установка пакетов на хостах.
    Плюсы:

    — Отсутствие проблем взаимодействия компонентов между собой, описанных для реализации в докер-контейнерах.
    — Возможность более гибкой настройки компонентов.

    Минусы:
    —Требуются дополнительные настройки для организации работы 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

Пара пояснений:

  1. [stdout_callback = yaml] — вывод при исполнении роли становится более читаемым;

  2. [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-нод кластера.

  • Формирование остальных групп — в соответствии с требуемой в вашей задаче реализацией.

Далее рассмотрим роли для деплоя каждого компонента

  1. 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]
  1. 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'ов, перезапускаемых при старте системы, так что в случае перезагрузки хостов, не потребуется ручного запуска.

При установке желательно придерживаться последовательности:

  1. cassandra-db

  2. cassandra-exporter — в этом случае cassandra-exporter сразу найдёт запущенный сервис; node-exporter от остальных компонентов не зависит, и его можно устанавливать в любой момент.

В заключение, для проверки работы каждого компонента:

  1. БД должна быть доступна при обращении к любой из нод кластера на порт 9042.

    — Для удалённого подключения через консоль нам понадобится клиент cqlsh:

$ cqlsh <cassandra_node1_ip-address>:9042 -u my_cass_user

Более подробно о работе с клиентом cqlsh.

— Для диагностики кластера при локальном подключении к хостам-нодам cassandra можно использовать инструмент — nodetool, устанавливается автоматически вместе с cassandra-db. Более подробно о работе с nodetool.

  1. Метрики node-exporter должны быть доступны при обращении к каждому хосту-ноде кластера на порт 9100,

  2. Метрики cassandra-exporter — на порт 9500.

Комментарии (0)