Kube-state-metrics et les Vertical Pod Autoscalers

Utilisez kube-state-metrics pour exploiter les métriques des VPAs

RV
Rémi Verchère
Platform & Cloud Native
7 min de lecture

Il y a quelque temps, je vous avais présenté Goldilocks et les Vertical Pod Autoscalers en mode recommandation (VPAs) pour vous aider à dimensionner vos applications. Il était même possible assez facilement de récupérer les métriques des VPAs grâce à l'agent kube-state-metrics, en rajoutant le collecteur verticalpodautoscalers dans la configuration.

Malheureusement, depuis la version 2.9.0 de l'agent, la collecte de ces ressources a été supprimée.

Il existe cependant un moyen, moins simple, de récupérer ces métriques, qui sont dans les status des ressources VPAs. À l'occasion de la mise à jour de ma stack de monitoring, je vous explique dans cet article comment faire.

Kube-state-metrics et les CustomResourceStateMetrics

Comme dit plus haut, kube-state-metrics est un composant applicatif qui se déploie dans un cluster kubernetes, et permet d'exposer des métriques sur l'état (status) d'objets kubernetes. Il est installé par défaut lorsque vous déployez la stack prometheus.

Vous retrouverez ici la liste des métriques qu'il permet d'exposer. On pourra par exemple avoir des informations sur le nombre de jobs créés, effectués, en erreur avec les métriques suivantes :

  • kube_job_created
  • kube_job_complete
  • kube_job_failed

L'application permet de générer beaucoup de métriques... Mais plus les VPAs ! Pas de panique, il est possible de s'en sortir avec une ressource particulière, les CustomResourceStateMetrics.

CustomResourceStateMetrics

Grâce aux CustomResourceStateMetrics, kube-state-metrics va aller chercher, dans les ressources spécifiées, les informations de status, et les exposer en tant que métriques.

Par exemple, prenons une ressource NodePool (nodepools.kube.cloud.ovh.com), qui permet de définir les nodepools et nombre de nœuds associés dans un cluster OVHcloud. Sur ces ressources sont indiquées dans le "status" le nombre de nœuds réellement disponibles dans le nodepool :

# kubectl get nodepools.kube.cloud.ovh.com general-worker-pool -o yaml
apiVersion: kube.cloud.ovh.com/v1alpha1
kind: NodePool
metadata:
  name: general-worker-pool
[...]
spec:
  desiredNodes: 4
  flavor: b2-15
  maxNodes: 100
  minNodes: 0
[...]
status:
  availableNodes: 4
  currentNodes: 4
  upToDateNodes: 4

On peut alors définir une CustomResourceStateMetrics qui ira chercher ces informations (availableNodes, currentNodes & upToDateNodes). Dans cette ressource, on va spécifier le type de ressource à lire (NodePool), où trouver les métriques (path), de quel type (gauge) :

kind: CustomResourceStateMetrics
spec:
  resources:
    - groupVersionKind: # Quelle ressource aller chercher
        group: kube.cloud.ovh.com
        kind: "NodePool"
        version: "v1alpha1"
      labelsFromPath:
        nodepool: [metadata, name]
      metrics:
        - name: "available_nodes_count"
          help: "Number of available nodes in the nodepool"
          each:
            type: Gauge # Quel type de métrique
            gauge:
              path: [status] # Path où trouver l'information
              valueFrom: [availableNodes]  # Valeur à récupérer
        - name: "current_nodes_count"
          help: "Number of current nodes in the nodepool"
          each:
            type: Gauge
            gauge:
              path: [status]
              valueFrom: [currentNodes]
        - name: "uptodate_nodes_count"
          help: "Number of up-to-date nodes in the nodepool"
          each:
            type: Gauge
            gauge:
              path: [status]
              valueFrom: [upToDateNodes]
              path: [status, availableNodes] # Path où trouver l'information

Cela génèrera trois métriques simples :

kube_customresource_available_nodes_count{customresource_group="kube.cloud.ovh.com",customresource_kind="NodePool",customresource_version="v1alpha1",nodepool="general-worker-pool"} 4
kube_customresource_current_nodes_count{customresource_group="kube.cloud.ovh.com",customresource_kind="NodePool",customresource_version="v1alpha1",nodepool="general-worker-pool"} 4
kube_customresource_uptodate_nodes_count{customresource_group="kube.cloud.ovh.com",customresource_kind="NodePool",customresource_version="v1alpha1",nodepool="general-worker-pool"} 4

Sur des ressources un peu plus complexes, on peut itérer avec des each, récupérer des labels, etc. Je vous renvoie à la documentation pour plus de détails.

Génération de métriques pour les VPAs

Si on revient à notre Vertical Pod Autoscaler, il va créer des ressources de type VerticalPodAutoscaler, dans lequel il stocke les informations relatives à ses recommandations (informations documentées ici) :

  • target : Demande de ressources mémoire et de processeur recommandée pour le conteneur.
  • lowerBound : Nombre minimum recommandé de demandes de ressources mémoire et de processeur pour le conteneur.
  • upperBound : Nombre maximum recommandé de demandes de ressources mémoire et de processeur pour le conteneur.
  • uncappedTarget : Dernière recommandation de ressources calculée par l'autoscaler, basée sur l'utilisation réelle des ressources, sans tenir compte de ContainerResourcePolicy.

Côté ressource yaml, cela donne :

# kubectl get vpa goldilocks-example -o yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  labels:
    creator: Fairwinds
    source: goldilocks
  name: goldilocks-example
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: example
  updatePolicy:
    updateMode: "Off"
status:
  conditions:
  - lastTransitionTime: "2023-08-21T08:16:27Z"
    status: "True"
    type: RecommendationProvided
  recommendation:
    containerRecommendations:
    - containerName: container
      lowerBound:
        cpu: 15m
        memory: "1101864149"
      target:
        cpu: 15m
        memory: "1238659775"
      uncappedTarget:
        cpu: 15m
        memory: "1238659775"
      upperBound:
        cpu: 25m
        memory: "1381172105"

Les informations sont donc la, recommandations CPU & RAM, on peut créer notre CustomResourceStateMetrics !

Installation pour kube-prometheus-stack

Je l'ai mentionné en début d'article, j'utilise la stack prometheus, installée avec le chart Helm kube-prometheus-stack. Il me faut alors rajouter l'ensemble des CustomResourceStateMetrics que je souhaite récupérer dans mon fichier values.yaml. Il faut également autoriser kube-state-metrics à aller scraper ces ressources. Ce qui nous donne (extrait) :

  • Pour le RBAC :

    # extract du fichier values.yaml
    kube-state-metrics:
      rbac:
        extraRules:
          - apiGroups: ["autoscaling.k8s.io"]
            resources: ["verticalpodautoscalers"]
            verbs: ["list", "watch"]
  • Pour les CustomResourceStateMetrics :

    # extract du fichier values.yaml
    kube-state-metrics:
      customResourceState:
        enabled: true
        config:
          kind: CustomResourceStateMetrics
          spec:
            resources:
              - groupVersionKind:
                  group: autoscaling.k8s.io
                  kind: "VerticalPodAutoscaler"
                  version: "v1"
                labelsFromPath:
                  verticalpodautoscaler: [metadata, name]
                  namespace: [metadata, namespace]
                  target_api_version: [spec, targetRef, apiVersion]
                  target_kind: [spec, targetRef, kind]
                  target_name: [spec, targetRef, name]
                metrics:
                  # Labels
                  - name: "verticalpodautoscaler_labels"
                    help: "VPA container recommendations. Kubernetes labels converted to Prometheus labels"
                    each:
                      type: Info
                      info:
                        labelsFromPath:
                          name: [metadata, name]
                  # Memory Information
                  - name: "verticalpodautoscaler_status_recommendation_containerrecommendations_target"
                    help: "VPA container recommendations for memory. Target resources the VerticalPodAutoscaler recommends for the container."
                    each:
                      type: Gauge
                      gauge:
                        path: [status, recommendation, containerRecommendations]
                        valueFrom: [target, memory]
                        labelsFromPath:
                          container: [containerName]
                    commonLabels:
                      resource: "memory"
                      unit: "byte"

Le fichier étant particulièrement long, vous retrouverez le paramétrage complet dans ce gist.

Il ne reste plus qu'à mettre à jour notre chart helm, et on aura les métriques que nous voulons. À date, je suis sur le chart 55.6.0, avec prometheus en version v0.70.0 :

$ helm upgrade prometheus prometheus-community/kube-prometheus-stack -f custom-values.yaml
Release "prometheus" has been upgraded. Happy Helming!
NAME: prometheus
LAST DEPLOYED: Wed Jan 10 09:26:19 2024
NAMESPACE: observability
STATUS: deployed
REVISION: 30
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
  kubectl --namespace observability get pods -l "release=prometheus"
 
Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
 
$ helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
prometheus      observability   30              2024-01-10 09:26:19.09370522 +0100 CET  deployed        kube-prometheus-stack-55.6.0    v0.70.0

On vérifie au démarrage du pod kube-state-metrics, à priori la configuration est prise en compte :

$ kubectl stern prom-kube-state-metrics-66dcd8b56b-snvz4
+ prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics
[...]
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:07.348917       1 wrapper.go:120] "Starting kube-state-metrics"
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:10.401886       1 registry_factory.go:76] "Info metric does not have _info suffix" gvk="autoscaling.k8s.io_v1_VerticalPodAutoscaler" name="verticalpodautoscaler_labels"
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:10.402053       1 config.go:82] "Using custom resource plural" resource="autoscaling.k8s.io_v1_VerticalPodAutoscaler" plural="verticalpodautoscalers"
[...]
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:10.403047       1 custom_resource_metrics.go:79] "Custom resource state added metrics" familyNames=["kube_customresource_verticalpodautoscaler_labels","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound","kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget"]
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:10.403134       1 builder.go:271] "Active resources" activeStoreNames="certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments,autoscaling.k8s.io/v1, Resource=verticalpodautoscalers"
prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics E0109 08:26:07.928093       1 registry_factory.go:682] "kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target" err="[status,recommendation,containerRecommendations]: got nil while resolving path"

Nous avons une erreur sur un path qui n'est pas trouvé, je n'ai pour l'instant pas cherché d'où venait cette erreur. Si vous lisez ces lignes et avez la solution, je suis preneur ;)

Nom des métriques

Avec l'utilisation des CustomResourceStateMetrics, on ne peut pas choisir le nom complet des métriques, elles seront toujours préfixées par kube_customresource.

Pour être le plus proche possible des anciennes métriques proposées, j'ai donc fait le choix de nommer les métriques ainsi :

  • kube_verticalpodautoscaler_labels -> kube_customresource_verticalpodautoscaler_labels
  • kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target -> kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target
  • kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound -> kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound
  • kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound -> kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound
  • kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget -> kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget

Résultat

Si l'on interroge kube-state-metrics directement, on peut voir les métriques :

$ kubectl port-forward svc/prom-kube-state-metrics 8080:8080
 
$ curl localhost:8080/metrics
[...]
# HELP kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target VPA container recommendations for memory. Target resources the VerticalPodAutoscaler recommends for the container.
# TYPE kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target gauge
kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target{container="storegateway",customresource_group="autoscaling.k8s.io",customresource_kind="VerticalPodAutoscaler",customresource_version="v1",namespace="observability",resource="memory",target_api_version="apps/v1",target_kind="StatefulSet",target_name="thanos-storegateway",unit="byte",verticalpodautoscaler="goldilocks-thanos-storegateway"} 1.048576e+08
kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target{container="node-exporter",customresource_group="autoscaling.k8s.io",customresource_kind="VerticalPodAutoscaler",customresource_version="v1",namespace="observability",resource="memory",target_api_version="apps/v1",target_kind="DaemonSet",target_name="prom-prometheus-node-exporter",unit="byte",verticalpodautoscaler="goldilocks-prom-prometheus-node-exporter"} 1.048576e+08
[...]

On se connecte sur Prometheus (ou même mieux, Grafana), et on a maintenant nos métriques, nickel !

Prometheus Query

Et bonus, le tableau de bord Grafana utilisant ces métriques a été mis à jour, vous le retrouverez ici : https://grafana.com/grafana/dashboards/16294-vpa-recommendations/

Ressources

Quelques liens m'ayant permis de mettre à jour les métriques :

© Gravitek. Tous droits réservés.

logo

Gravitek est une Société à taille humaine, guidée par la qualité de service et la construction d'une relation durable avec ses clients.