Auto-escalado de Druid con Kubernetes | Adaltas

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:




Enrollar

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:




Trabajadores en la interfaz de usuario del coordinador druida

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):




Interfaz de usuario de Prometheus

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:




Tareas pendientes de druida 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.

  • Add Your Comment