Это продолжение цикла, рассказывающего о практике развёртывания небольшого, но вполне производственного кластера 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, а не только весь этот скучный код.

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

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