Это продолжение цикла, рассказывающего о практике развёртывания небольшого, но вполне производственного кластера Cassandra. В первой, второй и третьей частях мы продвинулись вперед вот по такому плану:

  1. Анализ рабочей нагрузки и требований

  2. Разработка схемы данных

  3. Настройка хостовых машин

  4. Настройка конфигурации Cassandra

  5. Настройка топологии кластера

    = ВЫ НАХОДИТЕСЬ ЗДЕСЬ =

  6. Подключение Prometheus Cassandra Exporter

  7. Подключение Prometheus Node Exporter

  8. Вывод всех метрик в Grafana

  9. Проведение нагрузочного тестирования

  10. Дополнительный тюнинг по результатам теста

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

После этого материала у нас должны появиться два дашборда Grafana для обоих экспортеров. Традиционно забегая вперед скажу, что во время нагрузочного теста постоянно переключаться между парой вкладок браузера с дашбордами Cassandra и Node Monitor мне порядком надоело, поэтому я завёл 3 наиболее показательные метрики из Node Exporter’a (а именно: CPU Usage, Disk I/O Throughput, Disk I/O Utilization) непосредственно в дашборд Cassandra. Это оказалось очень удобно для мониторинга и вы можете поступить так же.

6. Подключение Prometheus Cassandra Exporter

При выборе этого экспортера появляются следующие муки выбора варианты:

  • Нативный экспортер именно для Cassandra

  • JVM Exporter, Grafana-дашборд которого “допиливается” до состояния, так сказать, “Cassandra Ready”

С первым всё понятно, а вот зачем может понадобиться второй вариант? А потому что именно он показывает подробности работы Garbage Collector’а JVM, что может очень пригодиться для тонкого тюнинга производительности Cassandra. Соответствующие настройки мы как раз обсуждали во 2-й статье цикла в разделе “Сборщик мусора”.

Идея тонкого тюнинга производственных деплоев мне очень близка, но есть нюанс: Небольшой рисёрч в каталоге дашбордов Grafana показал, что убить двух зайцев одним выстрелом не получится и нужно выбирать. Вариант с допиливанием подходящего JVM дашборда до состояния “Cassandra Ready” выглядел настолько трудоёмким, что я вздохнул и решил, что мне не очень-то и нужны графики с миллисекундами Garbage Collector по цене кропотливой недельной работы.

Замечу, что сделанный выбор оправдан только для текущего кейса, и в вашем случае (например, с кластером на петабайт+ данных) анализ метрик сборщика мусора JVM может иметь решающее значение для тюнинга производительности.

Короче говоря, я выбрал Bitnami Cassandra Exporter как наиболее обновляемый и поддерживаемый.

Правим настройки Cassandra Exporter

Вначале нам нужно забрать из выбранного образа исходный файл конфигурации экспортера, чтобы внести нужные правки. Например, так:

docker pull bitnamilegacy/cassandra-exporter:2-debian-11
docker run -d --name cassandra-exporter bitnami/cassandra-exporter:2-debian-11
docker cp cassandra-exporter:/opt/bitnami/cassandra-exporter/config.yml cassandra-exporter-config.yml

В этом небольшом конфиге потребуется поправить буквально несколько строк в самом начале файла. Должно получиться:

# deploy/templates/cassandra-exporter-config.yml

host: 127.0.0.1:7199
ssl: False
user:
password:
listenPort: 9103
...

Прочие строки оставьте как есть. Конфиг не использует подстановку Ansible-значений, поэтому здесь расширение файла yml (вместо j2).

Опции для экспортера

Подготовьте этот файл в указанном расположении:

# deploy/templates/jvm.options

# JMX options for Cassandra Exporter
-Dcom.sun.management.jmxremote.port=7199
-Dcom.sun.management.jmxremote.rmi.port=7199
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.local.only=false

Есть большой соблазн (которому я и поддался вначале) вместо еще одного отдельного конфига просто добавить нужные переменные среды в docker-compose.j2 внутрь секции services.cassandra.environment, но не делайте так!

Потому что переменные среды будут применяться ко всем компонентам Cassandra, включая утилиту nodetool, которой така�� оптимизация не понравится. Поэтому фабрики рабочим, земля крестьянам конфиг — экспортеру, окружение — Кассандре!

А доступы?

Проверьте, что ваш Prometheus доступен по нужному порту с каждой ноды. Здесь пример для случая с iptables— проделайте то же самое по аналогии с тем firewall, который фактически используется на хост-машинах всех нод:

sudo iptables -A INPUT -p tcp -s  --dport 9103 -j ACCEPT
sudo iptables -L -n | grep 9103

# Сохраним конфиг iptables, чтобы правки применялись при перезагрузке
sudo netfilter-persistent save
sudo cat /etc/iptables/rules.v4 | grep 9103

Обновляем docker-compose

Теперь добавим контейнер Cassandra Exporter в docker-compose.j2 и смонтируем пару только что подготовленных конфигов (показаны только вновь добавляемые строки):

# deploy/templates/docker-compose.j2
services:
  cassandra:
    volumes:
      - ./jvm.options:/etc/cassandra/jvm.options

  cassandra-exporter:
    image: bitnamilegacy/cassandra-exporter:2-debian-11
    container_name: cassandra-exporter
    restart: unless-stopped
    network_mode: "host"
    volumes:
      - ./cassandra-exporter-config.yml:/opt/bitnami/cassandra-exporter/config.yml
    depends_on:
      - cassandra

Можно сразу добавить Node Exporter, чтобы затем развернуть всё вместе.

7. Подключение Prometheus Node Exporter

К счастью, экспортер метрик с хост-машины Linux не требует таких мук выбора, как предыдущий.

Всё что нужно сделать — это смонтировать информацию о процессах, ядре, системе, устройствах, драйверах, а также всю корневую файловую систему хоста внутрь контейнера в режиме “только для чтения", чтобы Алиса экспортер мог получать эти данные и потом передавать куда следует.

Регулярка исключает из мониторинга служебные точки монтирования Docker и его внутренние /sys, /proc, /dev и прочие, чтобы не засорять метрики ненужными данными.

Давайте добавим всё это в docker-compose.j2 (показаны добавляемые строки):

# deploy/templates/docker-compose.j2
services:
  node-exporter:
    image: prom/node-exporter:v1.7.0
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)'
    ports:
      - "9100:9100"
    pid: "host"

Удобнее всего сначала настроить оба экспортера на какой-то одной выбранной ноде. Поскольку все нужные конфиги уже смонтированы на хост-машину, то в процессе отладки просто правим их “на лету” и перезапускаем ноду, чтобы изменения применились.

Если же ваш кластер под нагрузкой, то эксперименты только на одной ноде за раз — вообще единственный верный вариант. У вас же выставлен RF>1 для всех важных keyspaces, да? Да? (популярный_мем.jpg)

Если мы всё сделали правильно, то метрики на ноде уже должны появиться:

curl -s http://localhost:9103/metrics 2>/dev/null | head -n 10
curl -s http://localhost:9103/metrics | wc -l

Разворачиваем на весь кластер

Давайте обновим наш Ansible-плейбук:

# deploy/main.yml

- name: deploy
  hosts: all
  tasks:
	# ...
    - name: Copy all needed templates
      template:
        src: '{{ item.src }}'
        dest: '{{ item.dest }}'
        owner: '{{ item.owner | default("root") }}'
        group: '{{ item.group | default("root") }}'
        mode: '{{ item.mode | default("0644") }}'
      loop:
        - {src: 'templates/docker-compose.j2', 
           dest: '{{ ansible_env.HOME }}/cassandra/docker-compose.yml', 
           owner: 'root', 
           group: 'root', 
           mode: '0644'}
        - {src: 'templates/cassandra.j2', 
           dest: '{{ ansible_env.HOME }}/cassandra/cassandra.yaml', 
           owner: 'cassandra', 
           group: 'cassandra', 
           mode: '0644'}
        - {src: 'templates/jvm11-server.options.j2', 
           dest: '{{ ansible_env.HOME }}/cassandra/jvm11-server.options', 
           owner: 'cassandra', 
           group: 'cassandra', 
           mode: '0644'}
         - {src: 'templates/cassandra-rackdc.properties.j2', 
           dest: '{{ ansible_env.HOME }}/cassandra/cassandra-rackdc.properties', 
           owner: 'cassandra', 
           group: 'cassandra', 
           mode: '0644'}
        - {src: 'templates/jvm.options', 
           dest: '{{ ansible_env.HOME }}/cassandra/jvm.options', 
           owner: 'cassandra', 
           group: 'cassandra', 
           mode: '0600'}
        - {src: 'templates/cassandra-exporter-config.yml', 
           dest: '{{ ansible_env.HOME }}/cassandra/cassandra-exporter-config.yml', 
           owner: 'root', 
           group: 'root', 
           mode: '0644'}
      become: true

    - name: Create and start services
      docker_compose:
        project_src: '{{ ansible_env.HOME }}/cassandra'

Здесь еще появился файл cassandra-rackdc.properties.j2 с топологией кластера, который я неосмотрительно позабыл в прошлой статье цикла.

8. Вывод всех метрик в Grafana

Пожалуй, можно уже добавить в scrape configs существующего Prometheus вот такую секцию для Cassandra Exporter:

# prometheus.yml

scrape_configs:
  ...
  - job_name: "cassandra"
    static_configs:
      - targets: ["192.168.0.1:9103"]
        labels:
          instance: cassandra1
          environment: production
          service: cassandra
          datacenter: NIRS
          rack: rack1
      - targets: ["192.168.0.2:9103"]
        labels:
          instance: cassandra2
          environment: production
          service: cassandra
          datacenter: NIRS
          rack: rack1
      - targets: ["192.168.32.3:9103"]
        labels:
          instance: cassandra3
          environment: production
          service: cassandra
          datacenter: NIRS
          rack: rack2
      - targets: ["192.168.32.4:9103"]
        labels:
          instance: cassandra4
          environment: production
          service: cassandra
          datacenter: NIRS
          rack: rack2
      - targets: ["192.168.64.0:9103"]
        labels:
          instance: cassandra5
          environment: production
          service: cassandra
          datacenter: NIRS
          rack: rack2

И похожую секцию для Node Exporter туда же:

scrape_configs:
  ...
  - job_name: "cassandra_hosts"
    static_configs:
      - targets: ["192.168.0.1:9100"]
        labels:
          instance: cassandra1 # Same instance label as the Cassandra section
          environment: production
          service: cassandra-host
          datacenter: NIRS
          rack: rack1
      - targets: ["192.168.0.2:9100"]
        labels:
          instance: cassandra2
          environment: production
          service: cassandra-host
          datacenter: NIRS
          rack: rack1
      - targets: ["192.168.32.3:9100"]
        labels:
          instance: cassandra3
          environment: production
          service: cassandra-host
          datacenter: NIRS
          rack: rack2
      - targets: ["192.168.32.4:9100"]
        labels:
          instance: cassandra4
          environment: production
          service: cassandra-host
          datacenter: NIRS
          rack: rack2
      - targets: ["192.168.64.0:9100"]
        labels:
          instance: cassandra5
          environment: production
          service: cassandra-host
          datacenter: NIRS
          rack: rack2

Инстансы в метках есть смысл сделать одинаковыми в обеих секциях.

IP-адреса в примере взяты из предыдущей статьи, где мы настраивали топологию кластера, чтобы, так сказать, сохранить преемственность. А вот датацентр мы уже успели поменять, поскольку предыдущий сгорел. Новый ДЦ будет понадёжнее, как-никак.

Теперь добавьте в Grafana пару новых панелей из этих шаблонов ниже и настройте источники данных:

Подводим итог

Кластер в целом настроен и работает, а Grafana рисует графики с плоскими линиями, демонстрирующими, что ничего не происходит. Но мы уже на финишной прямой и скоро это исправим: в нагрузочном тесте всё пойдет куда интереснее, графики порадуют пиками в самых неожиданных местах, а кластер весело упадёт! В заключительной статье цикла наконец-то появятся инте��есные картинки из Playboy, а не только весь этот скучный код.

Хочешь, я уберу все длинные тире из этого текста, заменю “ё” на “е” и добавлю немного цитат Маяковского, чтобы не вызывать подозрений?

Окончание следует.