Apache Druid – колонкова база даних, орієнтована на роботу з великими обсягами даних, що поєднує в собі можливості та переваги Time-Series Database, Data Warehouse та пошукової системи.
Загальне завдання – налаштувати моніторинг кластера Druid в Kubernetes, для чого спочатку подивимося, що це взагалі таке і як воно все працює, а потім запустимо Друїд і помацаємо його руками.
Для запуску самого Druid у Kubernetes використовуємо druid-operator, для збору метрик – druid-exporter, для моніторингу – Kubernetes Prometheus Stack, а запускати все це будемо у Minikube.
Зміст
Огляд Apache Druid
Головна фіча Друїда особисто для мене це те, що система розроблялася для використання в хмарах типу AWS та Kubernetes, тому має чудові можливості зі скейлінгу, зберігання даних та відновлення роботи у разі падіння одного з сервісів.
Крім того, Друїд це:
- колонковий формат зберігання даних: для обробки завантажується лише необхідна таблиця, що значно збільшує швидкість обробки запитів. Крім того, для швидкого сканування та агрегації даних Друїд оптимізує сховище колонки відповідно до її типу даних.
- масштабована розподілена система: кластера Друїда можуть розташовуватися на десятках і сотнях окремих серверів
- паралельна обробка даних: Друїд може обробляти кожен запит паралельно на незалежних інстансах
- self-healing та self-balancing: у будь-який момент можна додати або видалити нові ноди в кластер, при цьому кластер сам виконує балансування запитів та перемикання між ними без будь-якого даунтайму. Більше того, Друїд дозволяє без даунтайму виконувати і оновлення версій
- власне, cloud-native и fault-tolerant архитектура: Друїд від початку спроектований до роботи у розподілених системах. Після отримання нових даних, він відразу ж копіює їх у свій Deep Storage, в ролі якого можуть бути сервіси зберігання даних типу Amazon S3, HDFS або мережна файлова система, і дозволяє відновити дані навіть у тому випадку, якщо всі друїдні сервери впадуть. Якщо впала лише частина серверів Друїда – вбудована реплікація даних дозволить продовжувати виконання запитів (вже сверблять руки повбивати його ноди, і подивитися на реакцію Друїда – але не цього разу)
- ідекси для швидкого пошуку: Друїд використовує стислі Bitmap-індекси, дозволяючи виконувати швидкий пошук по кількох колонках
- Time-based partitioning: за замовчуванням Друїд розділяє дані у часі, дозволяючи створювати додаткові сегменти з інших полях. В результаті, при виконанні запитів з тимчасовими проміжками будуть використані лише необхідні сегменти даних
Архітектура та компоненти Druid
Загальна архітектура:
Див. Processes and servers.
Three-server configuration – Master, Query, Data
Друїд має декілька внутрішніх сервісів, що використовуються для роботи з даними, і ці сервіси можна групувати за трьома типами серверів – Master, Query і Data, див. Pros and cons of colocation.
Master servers – управління додаванням нових даних та доступністю (ingest/indexing) – відповідають за запуск нових завдань щодо додавання нових даних та координацію доступності даних:
- Coordinator: відповідає за розміщення сегментів даних на конкретні ноди з Historical процесами
- Overlord: відповідають за розміщення завдань з додавання даних на Middle Managers і за координацію передачі створених сегментів у Deep Store
Query servers – управління запитами від клієнтів
- Broker: отримує запити від клієнтів, визначає які з Historical або Middle Manager процесів/нід містять необхідні сегменти, і з вихідного запиту клієнта формує та відправляє підзапит (sub-query) кожному з цих процесів, після чого отримує від них відповіді, формує з них загальну відповідь з даними для клієнта, і надсилає йому.
При цьому Historical відповідають на підзапити, які відносяться до сегментів даних, які вже зберігаються в Deep Store, а Middle Manager – відповідають на запити, які відносяться до нещодавно отриманих даних, які ще знаходяться в пам’яті і не відправлені в Deep Store - Router: опціональний сервіс, який надає загальний API для роботи з Брокерами, Оверлордами та Координаторами, хоча їх можна використовувати і безпосередньо. Крім того, Router надає Druid Console – WebUI для роботи з кластером, побачимо її трохи згодом
Data servers – керують додаванням нових даних у кластер і зберігають дані для клієнтів:
- Historical: “головні робочі конячки” (с) Друїда, які обслуговують сховище та запити до “історичних” даних, тобто. тим, які вже знаходяться в Deep Store – завантажують звідти сегменти на локальний диск хоста, на якому працює Historical процес, та формують відповіді за цими даними для клієнтів
- Middle Manager: обробляє додавання нових даних у кластер – читають дані із зовнішніх джерел та створюють з них нові сегменти даних, які потім завантажуються у Deep Store
- Peons: за додавання даних до кластера може відповідати кілька завдань. Для логування та ізоляції ресурсів Middle Manager створює Peons, які є окремими процесами JVM і відповідають за виконання конкретного завдання від Middle Manager. Запускається завжди на тому ж хості, де запущено процес Middle Manager, який породив Peon-процес
- Indexer: альтернатива Middle Manager і Peon – замість створення окремої JVM на кожне завдання від Middle Manager, Indexer виконує їх в окремих потоках своєї JVM.
Розробляється як простіша альтернатива Middle Manager і Peon, поки є експерементальною фічею, але в майбутньому замінить Middle Manager і Peon
Окрім внутрішніх процесів, для роботи Друїд також використовує зовнішні сервіси – Deep storage, Metadata storage та ZooKeeper:
- deep storage: використовується для зберігання всіх даних, доданих у систему і є розподіленим сховищем, доступним кожному серверу Друїда. Це можуть бути Amazon S3, HDFS або NFS
- metadata storage: використовується для зберігання внутрішніх метаданих, таких як інформація про використання сегментів та поточні завдання. У ролі такого сховища можуть бути класичні СУДБ типу PostgreSQL чи MySQL
- ZooKeeper: використовується для service discovery та координації роботи сервісів (у Kubernetes замість нього можна використовувати
druid-kubernetes-extensions
, спробуємо також, але в інший раз)
Druid data flow
Подивимося, як виконується додавання даних (Data ingest) та відповіді клієнтам (Data query, Query Answering).
Data ingest
Додавання даних можна розділити на два етапи: у першому, інстанси Middle Manager запускають завдання з індексування, ці завдання створюють сегменти даних і відправляють їх у Deep Store, а в другому – Historical інстанси завантажують сегменти даних із Deep store, щоб використовувати їх при формуванні відповіді на запити клієнтів.
Перший етап – отримання даних із зовнішніх джерел, індексування та створення сегментів даних, та їх завантаження в Deep store:
При цьому в процесі створення сегментів даних вони вже доступні для виконання запитів по них.
Другий етап – Coordinator опитує Metadata store у пошуках нових сегментів. Як тільки процес Coordinator-а їх знаходить, він вибирає інстанс Historical, який повинен завантажити сегмент з Deep store, щоб він став доступним для обробки запитів:
Coordinator опитує Metadata Store у пошуках нових сегментів. Як тільки він їх знаходить, Coordinator вибирає інстанс Historical, який має завантажити сегмент із Deep store. Коли сегмент завантажений – Historical готовий використовувати його для обробки запитів
Data query
Клієнти надсилають запити до Broker безпосередньо або через Router.
Коли такий запит отримано, Broker визначає, які процеси обслуговують необхідні сегменти даних.
Якщо дані вимагають сегментів одночасно і з Deep store (якісь старі), і з Middle Manager (які отримуємо прямо зараз зі стриму), то Брокер формує та надсилає окремі підзапити до Historical та Middle Manager, де кожен з них виконає свою частину та поверне дані Брокера. Брокер їх агрегує і відповідає клієнту з фінальним результатом.
Далі, спробуємо цю справу розгорнути і потикати гілочкою, а потім зверху додамо моніторингу.
Запуск Druid в Kubernetes
Запускаємо Minikube, додаємо пам’яті і ЦПУ, щоб спокійно запускати всі поди, тут виділяємо 24 RAM і 8 ядер – там Java, най їсть:
[simterm]
$ minikube start --memory 12000 --cpus 8 😄 minikube v1.26.1 on Arch "rolling" ✨ Automatically selected the virtualbox driver 👍 Starting control plane node minikube in cluster minikube 🔥 Creating virtualbox VM (CPUs=8, Memory=12000MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.24.3 on Docker 20.10.17 ... ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner, default-storageclass 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
[/simterm]
Перевіряємо:
[simterm]
$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: Configured
[/simterm]
І ноди:
[simterm]
$ kubectl get node NAME STATUS ROLES AGE VERSION minikube Ready control-plane 41s v1.24.3
[/simterm]
Переходимо до встановлення Apache Druid operator.
Установка Druid Operator
Створюємо Namespace для оператора:
[simterm]
$ kubectl create namespace druid-operator namespace/druid-operator created
[/simterm]
Встановлюємо оператор у цей неймспейс:
[simterm]
$ git clone https://github.com/druid-io/druid-operator.git $ cd druid-operator/ $ helm -n druid-operator install cluster-druid-operator ./chart
[/simterm]
Перевіряємо под:
[simterm]
$ kubectl -n druid-operator get pod NAME READY STATUS RESTARTS AGE cluster-druid-operator-9c8c44f78-8svhc 1/1 Running 0 49s
[/simterm]
Переходимо до створення кластера.
Запуск Druid Cluster
Створюємо неймспейс:
[simterm]
$ kubectl create ns druid namespace/druid created
[/simterm]
Встановлюємо ZooKeeper:
[simterm]
$ kubectl -n druid apply -f examples/tiny-cluster-zk.yaml service/tiny-cluster-zk created statefulset.apps/tiny-cluster-zk created
[/simterm]
Для створення тестового кластера використовуємо конфіг із прикладів – examples/tiny-cluster.yaml
.
Створюємо кластер:
[simterm]
$ kubectl -n druid apply -f examples/tiny-cluster.yaml druid.druid.apache.org/tiny-cluster created
[/simterm]
Перевіряємо ресурси:
[simterm]
$ kubectl -n druid get all NAME READY STATUS RESTARTS AGE pod/druid-tiny-cluster-brokers-0 0/1 ContainerCreating 0 21s pod/druid-tiny-cluster-coordinators-0 0/1 ContainerCreating 0 21s pod/druid-tiny-cluster-historicals-0 0/1 ContainerCreating 0 21s pod/druid-tiny-cluster-routers-0 0/1 ContainerCreating 0 21s pod/tiny-cluster-zk-0 1/1 Running 0 40s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/druid-tiny-cluster-brokers ClusterIP None <none> 8088/TCP 21s service/druid-tiny-cluster-coordinators ClusterIP None <none> 8088/TCP 21s service/druid-tiny-cluster-historicals ClusterIP None <none> 8088/TCP 21s service/druid-tiny-cluster-routers ClusterIP None <none> 8088/TCP 21s service/tiny-cluster-zk ClusterIP None <none> 2181/TCP,2888/TCP,3888/TCP 40s NAME READY AGE statefulset.apps/druid-tiny-cluster-brokers 0/1 21s statefulset.apps/druid-tiny-cluster-coordinators 0/1 21s statefulset.apps/druid-tiny-cluster-historicals 0/1 21s statefulset.apps/druid-tiny-cluster-routers 0/1 21s statefulset.apps/tiny-cluster-zk 1/1 40s
[/simterm]
Чекаємо, поки піди перейдуть у Running (хвилин 5 зайняло), і прокидаємо порт до Druid Router:
[simterm]
$ kubectl port-forward svc/druid-tiny-cluster-routers 8888:8088 -n druid Forwarding from 127.0.0.1:8888 -> 8088 Forwarding from [::1]:8888 -> 8088
[/simterm]
Відкриваємо Druid Console – http://localhost:8888:
Можна заглянути в Druid Services – побачимо ті ж сервіси, що бачили у вигляді Kubernetes-подів:
Ось тут є непогане коротке відео з прикладом завантаження тестових даних – заради інтересу можна пройтися.
Переходимо до встановлення Prometheus.
Установка Kube Prometheus Stack
Створюємо неймспейс:
[simterm]
$ kubectl create ns monitoring namespace/monitoring created
[/simterm]
Встановлюємо KPS:
[simterm]
$ helm -n monitoring install kube-prometheus-stack prometheus-community/kube-prometheus-stack NAME: kube-prometheus-stack LAST DEPLOYED: Tue Sep 13 15:24:22 2022 NAMESPACE: monitoring STATUS: deployed ...
[/simterm]
Перевіряємо поди:
[simterm]
$ kubectl -n monitoring get po NAME READY STATUS RESTARTS AGE alertmanager-kube-prometheus-stack-alertmanager-0 0/2 ContainerCreating 0 22s kube-prometheus-stack-grafana-595f8cff67-zrvxv 3/3 Running 0 42s kube-prometheus-stack-kube-state-metrics-66dd655687-nkxpb 1/1 Running 0 42s kube-prometheus-stack-operator-7bc9959dd6-d52gh 1/1 Running 0 42s kube-prometheus-stack-prometheus-node-exporter-rvgxw 1/1 Running 0 42s prometheus-kube-prometheus-stack-prometheus-0 0/2 Init:0/1 0 22s
[/simterm]
Чекаємо кілька хвилин, поки всі стануть Running і відкриваємо собі доступ до Prometheus:
[simterm]
$ kubectl -n monitoring port-forward svc/kube-prometheus-stack-prometheus 9090:9090 Forwarding from 127.0.0.1:9090 -> 9090 Forwarding from [::1]:9090 -> 9090
[/simterm]
Відкриваємо собі доступ до Grafana:
[simterm]
$ kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 8080:80 Forwarding from 127.0.0.1:8080 -> 3000 Forwarding from [::1]:8080 -> 3000
[/simterm]
Grafana dashboard
Отримуємо пароль від Grafana – знаходимо її Secret:
[simterm]
$ kubectl -n monitoring get secret | grep graf kube-prometheus-stack-grafana Opaque 3 102s
[/simterm]
І отримуємо значення:
[simterm]
$ kubectl -n monitoring get secret kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo prom-operator
[/simterm]
Для Grafana є готова community дашборда, поки додамо її, а взагалі потім зробимо свою.
Відкриваємо в браузері localhost:8080, логінимся, переходимо в Dashboards > Import:
Вказуємо ID 12155, завантажуємо:
ВибираємоPrometheus як datasource:
Отримуємо таку борду, але поки що без даних:
Запуск Druid Exporter
Клонуємо репозиторій:
[simterm]
$ cd ../ $ git clone https://github.com/opstree/druid-exporter.git $ cd druid-exporter/
[/simterm]
Перевіряємо Druid Router Service – нам треба його повне ім’я, щоб налаштувати Exporter:
[simterm]
$ kubectl -n druid get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... druid-tiny-cluster-routers ClusterIP None <none> 8088/TCP 4m3s ...
[/simterm]
Встановлюємо експортер в неймспейсmonitoring, в параметрі druidURL
вказуємо URL сервісу Router нашого Друїд-кластера, дивилися вище, його порт, включаємо створення Kubernetes ServiceMonitor та неймспейс, в якому у нас працює Prometheus (monitoring):
[simterm]
$ helm -n monitoring install druid-exporter ./helm/ --set druidURL="http://druid-tiny-cluster-routers.druid.svc.cluster.local:8088" --set druidExporterPort="8080" --set logLevel="debug" --set logFormat="text" --set serviceMonitor.enabled=true --set serviceMonitor.namespace="monitoring" NAME: druid-exporter LAST DEPLOYED: Tue Sep 13 15:28:25 2022 NAMESPACE: monitoring STATUS: deployed ...
[/simterm]
Відкриваємо доступ до Exporter Service:
[simterm]
$ kubectl -n monitoring port-forward svc/druid-exporter-prometheus-druid-exporter 8989:8080 Forwarding from 127.0.0.1:8989 -> 8080 Forwarding from [::1]:8989 -> 8080
[/simterm]
Перевіряємо метрики:
[simterm]
$ curl -s localhost:8989/metrics | grep -v '#' | grep druid_ druid_completed_tasks 0 druid_health_status{druid="health"} 1 druid_pending_tasks 0 druid_running_tasks 0 druid_waiting_tasks 0
[/simterm]
Окей – вже щось, хоча поки що мало. Додаємо збір даних у Prometheus, а потім додамо ще метрик.
Prometheus ServiceMonitor
Перевіряємо Prometheus Service Discovery – тут повинні бути всі наші СервісМонітори:
Перевірити, чи створено Druid ServiceMonitor в Kubernetes :
[simterm]
$ kubectl -n monitoring get servicemonitors NAME AGE druid-exporter-prometheus-druid-exporter 87s ...
[/simterm]
Перевіряємо ресурс Prometheus - йогоserviceMonitorSelector
:
[simterm]
$ kubectl -n monitoring get prometheus -o yaml | yq .items[].spec.serviceMonitorSelector.matchLabels { "release": "kube-prometheus-stack" }
[/simterm]
Редагуємо Druid ServiceMonitor:
[simterm]
$ kubectl -n monitoring edit servicemonitor druid-exporter-prometheus-druid-exporter
[/simterm]
Додаємо лейблу release: kube-prometheus-stack
, щоб Prometheus почав з цього ServiceMonitor збирати метрики:
Чекаємо на хвилину-дві, перевіряємо монітори ще раз:
Перевіряємо метрики:
Окей – тепер у нас є Druid Cluster, є Prometheus, який збирає метрики – залишилося налаштувати ці самі метрики.
Apache Druid Monitoring
Налаштування моніторингу Друїда включає Emitters і Monitors: емітери “пушать” метрики з Друїда “назовні”, а монітори визначають те, які саме метрики будуть доступні. При цьому для деяких метрик потрібно включати додаткові розширення.
Див:
Усі сервіси мають загальні метрики, наприклад query/time
, та свої власні, наприклад для Broker є метрика sqlQuery/time
, які можуть бути задані для конкретного сервісу через його runtime.properties
.
Druid Metrics Emitters
Щоб включити “емітинг” метрик – редагуємо examples/tiny-cluster.yaml
та в common.runtime.properties
додаємо:
druid.emitter=http druid.emitter.logging.logLevel=debug druid.emitter.http.recipientBaseUrl=http://druid-exporter-prometheus-druid-exporter.monitoring.svc.cluster.local:8080/druid
Зберігаємо, оновлюємо кластер:
[simterm]
$ kubectl -n druid apply -f examples/tiny-cluster.yaml druid.druid.apache.org/tiny-cluster configured
[/simterm]
Чекаємо хвилину-дві, щоб піди перезапустилися і почали збиратися метрики, перевіряємо метрики в Експортері:
[simterm]
$ curl -s localhost:8989/metrics | grep -v '#' | grep druid_ | head druid_completed_tasks 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-brokers-0:8088",metric_name="avatica-remote-JsonHandler-Handler/Serialization",service="druid-broker"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-brokers-0:8088",metric_name="avatica-remote-ProtobufHandler-Handler/Serialization",service="druid-broker"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-brokers-0:8088",metric_name="avatica-server-AvaticaJsonHandler-Handler/RequestTimings",service="druid-broker"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-brokers-0:8088",metric_name="avatica-server-AvaticaProtobufHandler-Handler/RequestTimings",service="druid-broker"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-brokers-0:8088",metric_name="jetty-numOpenConnections",service="druid-broker"} 1 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-coordinators-0:8088",metric_name="compact-task-count",service="druid-coordinator"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-coordinators-0:8088",metric_name="compactTask-availableSlot-count",service="druid-coordinator"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-coordinators-0:8088",metric_name="compactTask-maxSlot-count",service="druid-coordinator"} 0 druid_emitted_metrics{datasource="",host="druid-tiny-cluster-coordinators-0:8088",metric_name="coordinator-global-time",service="druid-coordinator"} 2
[/simterm]
Появились druid_emitted_metrics
– чудово.
У них у лейбліexported_service
вказується з якого сервісу метрика отримана, а в metric_name
– яка саме метрика.
Перевіряємо в Prometheus:
Чудово!
Але хочеться більше за метрик – “Need more metrics, my lord!” (c)
Druid Monitors
Наприклад, хочеться знати споживання CPU кожним із процесів/сервісом.
Щоб мати можливість бачити всі системні метрики (без Prometheus Node Exporter) – включаємо org.apache.druid.java.util.metrics.SysMonitor
, а для даних по роботі JVM – додамо org.apache.druid.java.util.metrics.JvmMonitor
.
У той же файл examples/tiny-cluster.yaml
до блоку common.runtime.properties
додаємо:
... druid.monitoring.monitors=["org.apache.druid.java.util.metrics.SysMonitor", "org.apache.druid.java.util.metrics.JvmMonitor"] ...
Зберігаємо, оновлюємо кластер, чекаємо на рестарт подів, і через пару хвилин перевіряємо метрики:
Інша справа!
І дашборда в Графані:
Чудово.
У Running/Failed Tasks у нас все ще No Data, тому що нічого не запускали. Якщо завантажити тестові дані із вже згаданого відео – то з’являться та ці метрики.
Залишилося насправді ще багато чого:
- додати PostgreSQL як Metadata store
- налаштувати збір метрик з нього
- протестувати роботу без ZooKeeper (
druid-kubernetes-extensions
) - потестуватиPrometheus Emitter
- спробувати Indexer замість Middle Manager
- налаштувати збір логів (куди? поки не придумали, але швидше за все Promtail && Loki)
- ну і власне – зібрати нормальну дашборду для Графани
А вже маючи моніторинг можна буде поганяти якісь тести навантаження і погратися з тюнінгом продуктивності кластера, але це все вже в наступних частинах.
Посилання по темі
- How Netflix uses Druid for Real-time Insights to Ensure a High-Quality Experience
- Apache Druid (part 1): A Scalable Timeseries OLAP Database System
- Data Ingestion in Druid – Overview
- Learning about the Druid Architecture
- Druid Architecture & Concepts
- Event-Driven Data with Apache Druid
- Apache Druid: Setup, Monitoring and Auto Scaling on Kubernetes
- Apache Druid: Interactive Analytics at Scale
- Apache Druid (incubating) Configuration Reference
- Running Apache Druid in Kubernetes
- Setting up Apache Druid on Kubernetes in under 30 minutes
- Running Apache Druid in Kubernetes