Apache Druid es un almacén de datos de análisis de código abierto que podría aprovechar las capacidades de escalado automático de Kubernetes debido a su naturaleza distribuida y su dependencia de la memoria. Me inspiré en la charla “Apache Druid Auto Scale-out / in para la ingestión de datos en streaming en Kubernetes”Por Jinchul Kim durante DataWorks Summit 2019 Europe en Barcelona.
druida
druida es un almacén de datos distribuido, de código abierto y orientado a columnas que se utiliza comúnmente en aplicaciones de inteligencia empresarial / OLAP para analizar grandes volúmenes de datos históricos y en tiempo real. Puede verse como una mezcla entre un motor de búsqueda como Apache Solr y Elasticsearch, una base de datos de series de tiempo (TSDB) como Prometeo, OpenTSDB y un OLAP Base de datos. Es actualmente incubando para convertirse en un proyecto Apache de alto nivel. Un grupo de druidas está fragmentado en múltiples roles / servicios que están diseñados para ser altamente escalables. Más importante aún, debido a las especificidades de su arquitectura e implementación, Druid es De Verdad rápido cuando se trata de casos de uso específicos.
Ingestión acumulada
Druid tiene una función de agregación previa llamada “Rollup”. En el momento de la ingestión, el sistema no almacenará registros individuales. En cambio, almacena agregaciones de estos registros en función de las dimensiones de los datos, como podemos ver en este ejemplo:
Estos registros se almacenan como segmentos en forma de columnas. La combinación de estas dos características es lo que hace que Druid sea tan rápido para consultas analíticas como count o groupBys porque los datos ya se calcularon en el momento de la ingestión. La orientación en columnas también es práctica porque solo lee los datos que necesita para responder la consulta.
Roles
Una instalación de Druid está compuesta por varios servicios diseñados para ejecutarse en una arquitectura distribuida y compatible con la nube.
- Middlemanager: responsable de la ingesta de datos, lectura de fuentes de datos externas en segmentos. Este es el servicio en el que nos centraremos en este artículo para intentar escalarlo automáticamente con Kubernetes.
- Histórico: maneje el almacenamiento y responda las consultas sobre datos ya ingeridos (es decir, datos históricos).
- Broker: Reciba solicitudes de ingestión y consulta y reenvíelas al servicio adecuado (histórico o intermediario).
- Overlord: ocuparse de la asignación de tareas de ingestión a los nodos Middlemanager.
- Coordinador: ocúpese del equilibrio de los segmentos en los nodos históricos del clúster.
Puede parecer complejo a primera vista, pero con un poco de práctica de Druid, cada rol parecerá una obviedad.
Escalado automático
Druid tiene una capacidad de escala automática incorporada, pero desafortunadamente, la única implementación en el momento de escribir este artículo es acoplado con Amazon EC2. Existe una necesidad real de que los clústeres de Druid admitan el escalado automático en entornos complementarios, como para otros proveedores de la nube y en plataformas nativas de Kubernetes.
Kubernetes
Kubernetes (K8s) es un orquestador de contenedores. Si no está demasiado familiarizado con Kubernetes o desea actualizarse, lo invito a leer el excelente artículo de Arthur. “Instalación de Kubernetes en CentOS 7”. Un aspecto de Kubernetes que no se trata en este artículo es la capacidad de escalar automáticamente. Veamos cómo funciona esto.
Escalador automático de pod horizontal
Escalador automático de pod horizontal (HPA) es una función que permite al usuario permitir que Kubernetes aumente o disminuya automáticamente la cantidad de pods en un Controlador de replicación, Despliegue o ReplicaSet. Se basa de forma predeterminada en el uso de la CPU mediante la API metrics.k8s.io proporcionada por servidor de métricas (ya que Heapster como obsoleto) y se puede ampliar con métricas personalizadas definidas por el usuario utilizando el API custom.metrics.k8s.io.
El algoritmo predeterminado para decidir el número de réplicas es el siguiente:
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
Ejemplo en un contexto druida:
Los Middlemanagers son responsables de realizar la ingestión en formas de tareas. Como las tareas las realiza un Middlemanager de una en una, podemos declarar que una instalación de Druid con muchas tareas pendientes (es decir, tareas que esperan que haya un Middlemanager disponible) no se escale correctamente.
Con:
– currentReplicas
: número de mandos intermedios, 3 al principio.
– desiredMetricValue
: número de tareas pendientes que queremos, decidamos que 5 es aceptable.
– currentMetricValue
: número de tareas pendientes, en el momento del cálculo 10.
desiredReplicas = ceil[3 * (10 / 5)] = 6
En este ejemplo, Kubernetes se ampliará por los pods de Middlemanager veces 2, lo que dará como resultado 6 Middlemanagers para manejar la carga de trabajo.
Con esta fórmula, cero tareas pendientes conduciría a cero réplicas del Middlemanager que obviamente no es lo que queremos. Kubernetes puede establecer un límite mínimo estricto para nosotros, como veremos en la parte de demostración del artículo.
API de métricas personalizadas
Kuberenetes proporciona una API para métricas definidas por el usuario. Esta API se puede implementar para brindar métricas personalizadas que se pueden usar para las capacidades integradas de Kubernetes; en nuestro caso, estamos interesados en usarlas con la HPA.
Hay algunas implementaciones disponibles conocidas como “adaptadores”. Usaremos el Prometheus adaptador diseñada por DirectXMan12. Prometheus es Cloud Native Computing Foundation (CNCF) se ha convertido en un estándar en términos de extracción, análisis y base de datos de métricas.
También vale la pena mencionar que hay un texto estándar si desea implementar su propia API de métricas personalizadas.
Manifestación
Para esta demostración, implementé un clúster de Kubernetes de 3 trabajadores siguiendo el tutorial de Arthur. También instalé un clúster Druid en este Kubernetes usando Helm’s Druid gráfico. Timón es un administrador de paquetes para Kubernetes que simplifica muchas cosas. Nos ayuda a implementar aplicaciones comunes en Kubernetes sin reinventar la rueda.
Implementé un grupo de druidas usando la tabla de Helm en incubación. Aquí hay un vistazo a nuestro grupo:
kubectl get pods -n druid -o wide
NAME READY STATUS RESTARTS AGE IP NOMINATED NODE READINESS GATES
druid-broker-5c6b4dd495-ppmvx 1/1 Running 2 65m 10.244.3.141 <none> <none>
druid-coordinator-748f4fd656-vkvjq 1/1 Running 1 65m 10.244.2.133 <none> <none>
druid-historical-0 1/1 Running 0 65m 10.244.3.143 <none> <none>
druid-middle-manager-0 1/1 Running 0 65m 10.244.3.144 <none> <none>
druid-middle-manager-1 1/1 Running 0 66m 10.244.3.146 <none> <none>
druid-middle-manager-2 1/1 Running 0 67m 10.244.3.147 <none> <none>
druid-mysql-6764889c67-f7l5r 1/1 Running 0 65m 10.244.2.131 <none> <none>
druid-overlord-5fcd7c49cd-nh764 1/1 Running 1 65m 10.244.3.142 <none> <none>
druid-zookeeper-0 1/1 Running 0 65m 10.244.2.132 <none> <none>
druid-zookeeper-1 1/1 Running 0 47h 10.244.3.145 <none> <none>
druid-zookeeper-2 1/1 Running 0 65m 10.244.1.147 <none> <none>
Como podemos ver, tenemos 3 Middlemanagers, por lo que estamos en una situación nominal con nuestras Réplicas actuales en el cálculo anterior.
La interfaz de usuario web del Coordinador puede confirmar que:
Para comenzar, usaremos stefanprodan’s k8s-prom-hpa Proyecto GitHub, ya que es un excelente punto de partida para usar HPA con métricas personalizadas de Prometheus. Contiene la mayoría de los recursos que necesitamos para este caso de uso.
Creemos una implementación de Prometheus en nuestro clúster de Kubernetes:
kubectl create -f prometheus/
configmap/prometheus-config created
deployment.apps/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
serviceaccount/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
service/prometheus created
Nuestro Prometheus ahora es accesible a través del puerto configurado en ./prometheus/prometheus-svc.yaml
(31990):
Podemos ver en la pestaña “Gráfico” que ya tenemos muchas métricas interesantes de Kubernetes: uso de CPU, uso de memoria, uso de disco, etc. Es porque los raspadores de Prometheus están configurados para leer directamente desde la API REST de Kubernetes usando
como podemos ver en ./prometheus/prometheus-cfg.yaml
.
También hay configuraciones adicionales para modificar el etiquetado y los nombres de las métricas.
Estas métricas son buenas, pero todavía no hay nada que nos permita escalar automáticamente Druid en función de los requisitos previos de nuestro ejemplo anterior.
Ahora necesitamos recopilar métricas de Druid y hacer que Prometheus las elimine.
Para este propósito y como es solo un POC y no algo que quiera ejecutar en producción, escribí un De Verdad exportador simple de Prometheus para exponer solo una métrica, aquí está el código:
http = require('http')
axios = require('axios')
get_num_pending_tasks = () ->
axios.get "http://#env.HOST:#env.PORT/druid/indexer/v1/pendingTasks"
.then (response) ->
return response.data.length
server = http.createServer (req, res) ->
res.writeHead 200
res.end """
container_druid_num_pending_tasks #await get_num_pending_tasks()
"""
return
server.listen 8080
Luego podemos configurar un raspador de Prometheus para obtener estas métricas, esto sucederá en ./prometheus/prometheus-cfg.yml como el archivo de configuración (/etc/prometheus/prometheus.yml) de la implementación de Prometheus que lanzamos en Kubernetes se define con un ConfigMap:
...
- job_name: 'druid_prometheus_exporter'
metrics_path: /
scheme: http
static_configs:
- targets:
- edge01.metal.ryba:49160
labels:
container_name: 'druid'
pod_name: 'druid-middle-manager-0'
namespace: 'druid'
...
Tenga en cuenta que le estamos diciendo a Prometheus que agregue etiquetas a estas métricas.
Después de reiniciar Prometheus, podemos ver la métrica que aparece en Prometheus:
Ahora estamos listos para implementar el adaptador Prometheus:
kubectl create -f custom-metrics-api/
secret/cm-adapter-serving-certs created
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics:system:auth-delegator create
rolebinding.rbac.authorization.k8s.io/custom-metrics-auth-reader created
deployment.extensions/custom-metrics-apiserver create
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics-resource-reader created
serviceaccount/custom-metrics-apiserver created
service/custom-metrics-apiserver created
apiservice.apiregistration.k8s.io/v1beta1.custom.metrics.k8s.io created
clusterrole.rbac.authorization.k8s.io/custom-metrics-server-resources created
clusterrole.rbac.authorization.k8s.io/custom-metrics-resource-reader created
clusterrolebinding.rbac.authorization.k8s.io/hpa-controller-custom-metrics created
El adaptador consulta las métricas de Prometheus, las analiza y las pone a disposición a través de la API de métricas personalizadas. Veamos si podemos recuperar nuestra métrica:
kubectl get --raw "/api/custom.metrics.k8s.io/v1beta1/namespaces/druid/pods/*/druid_num_pending_tasks"
"kind": "MetricValueList","apiVersion":"custom.metrics.k8s.io/v1beta1","metadata":"selfLink":"/apis/custom.metrics.k8s.io/v1beta1/namespaces/druid/pods/%2A/druid_num_pending_tasks","items":["describedObject":"kind":"Pod","namespace":"druid","name":"druid-middle-manager-0","apiVersion":"/__internal","metricName":"druid_num_pending_tasks","timestamp":"2019-04-17T13:08:45Z","value":"3"]
Funciona, podemos ver que la cantidad de tareas pendientes (actualmente 3) ahora es una métrica nativa de Kubernetes.
Ahora podemos crear el Escalador automático de vaina horizontal, así es como se ve:
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
namespace: druid
name: druid-mm
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: druid-middle-manager
minReplicas: 3
maxReplicas: 16
metrics:
- type: Pods
pods:
metricName: druid_num_pending_tasks
targetAverageValue: 5
Es bastante sencillo, solo necesitamos definir lo siguiente:
– Un nombre para la HPA.
– Un objetivo en el que se aplicará: aquí, nuestro StatefulSet druid-middle-manager.
– Un número mínimo y máximo de réplicas: esto es útil para evitar que el HPA escale como un loco en una dirección u otra y se salga de las manos.
– Una métrica con su valor preferido, a partir del cual la HPA calculará el número de réplicas preferidas.
Unos segundos después de crear el HPA, podemos describir este recurso de Kubernetes para ver cómo se está comportando:
Actualmente tenemos 4 tareas pendientes, la HPA nos dice que es aceptable comparado con el objetivo (targetAverageValue) que nos hemos marcado:
kubectl describe -f druid/middlemanager-hpa.yaml
Name: druid-mm
Namespace: druid
Labels: <none>
Annotations: <none>
CreationTimestamp: Wed, 17 Apr 2019 13:41:50 +0000
Reference: StatefulSet/druid-middle-manager
Metrics: ( current / target )
"druid_num_pending_tasks" on pods: 4 / 5
Min replicas: 1
Max replicas: 16
StatefulSet pods: 3 current / 3 desired
Conditions:
Type Status Reason Message
---- ------ ------ --------
AbleToScale True ReadyForNewScale recommended size matches current size
ScalingActive True ValidMetricFound the HPA was able to successfully calculate a replica count from pods metric druid_num_pending_tasks
ScalingLimited False DesiredWithinRange the desired count is within the acceptable range
Events: <none>
Ahora intentemos llevar a nuestro druida un poco más allá activando muchas tareas de ingestión al mismo tiempo. Después de unos segundos, la descripción de HPA debería verse así:
kubectl describe hpa druid-mm -n druid
Name: druid-mm
Namespace: druid
Labels: <none>
Annotations: <none>
CreationTimestamp: Wed, 17 Apr 2019 13:59:33 +0000
Reference: StatefulSet/druid-middle-manager
Metrics: ( current / target )
"druid_num_pending_tasks" on pods: 25 / 5
Min replicas: 1
Max replicas: 16
StatefulSet pods: 3 current / 6 desired
Conditions:
Type Status Reason Message
---- ------ ------ --------
AbleToScale True SucceededRescale the HPA controller was able to update the target scale to 6
ScalingActive True ValidMetricFound the HPA was able to successfully calculate a replica count from pods metric druid_num_pending_tasks
ScalingLimited True ScaleUpLimit the desired replica count is increasing faster than the maximum scale rate
Events: <none>
Y finalmente, después de esperar unos segundos más:
kubectl get pods -n druid -o wide
NAME READY STATUS RESTARTS AGE IP NOMINATED NODE READINESS GATES
druid-broker-5c6b4dd495-ppmvx 1/1 Running 66 47h 10.244.3.141 <none> <none>
druid-coordinator-748f4fd656-vkvjq 1/1 Running 1 47h 10.244.2.133 <none> <none>
druid-historical-0 1/1 Running 40 47h 10.244.3.143 <none> <none>
druid-middle-manager-0 1/1 Running 3 47h 10.244.3.144 <none> <none>
druid-middle-manager-1 1/1 Running 0 5h38m 10.244.3.148 <none> <none>
druid-middle-manager-2 1/1 Running 0 30m 10.244.3.150 <none> <none>
druid-middle-manager-3 1/1 Running 0 5m 10.244.3.150 <none> <none>
druid-middle-manager-4 1/1 Running 0 4m 10.244.3.150 <none> <none>
druid-middle-manager-5 1/1 Running 0 3m 10.244.3.150 <none> <none>
druid-mysql-6764889c67-f7l5r 1/1 Running 0 47h 10.244.2.131 <none> <none>
druid-overlord-5fcd7c49cd-nh764 1/1 Running 0 47h 10.244.3.142 <none> <none>
druid-zookeeper-0 1/1 Running 0 47h 10.244.2.132 <none> <none>
druid-zookeeper-1 1/1 Running 0 47h 10.244.3.145 <none> <none>
druid-zookeeper-2 1/1 Running 0 47h 10.244.1.147 <none> <none>
¡Lo hicimos! StatefulSet se ha ampliado y ahora tenemos 6 Middlemanagers en funcionamiento para equilibrar la carga.
¿Que sigue?
Como demostró esta demostración, el escalado automático de Druid con Kubernetes es posible, pero hay algunas cosas que podríamos haber hecho mejor. Para empezar, podríamos (y deberíamos) tener mejores exportadores de Prometheus para las métricas de Druid, como el que usamos en la demostración es muy limitado. Esta proyecto de Wikimedia parece interesante, es un punto final configurable para Druid’s módulo-emisor-http, recibe las métricas y las expone en un formato compatible con Prometheus. Esta aplicación de Python tendría que estar Dockerizada para poder ejecutarse junto con nuestro clúster en Kubernetes. El gráfico de Helm que usamos para implementar Druid también podría necesitar una pequeña modificación para ser lo suficientemente modificable como para admitir esta configuración.
La HPA que configuramos escaló los MiddleManagers para los datos de ingestión. También podríamos imaginar un proceso similar para la consulta de estos datos. Al monitorear las métricas de lectura del clúster, podríamos escalar automáticamente el rol de Druid Historical para atender a más clientes al mismo tiempo.
HPA es una buena solución para el escalado automático de Druid, pero no es realmente viable para el clúster de Druid existente. Con suerte, el equipo de desarrollo detrás del proyecto tendrá una implementación más abierta que la de EC2.