Kube-state-metrics et les Vertical Pod Autoscalers

Sommaire

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 :

 1# kubectl get nodepools.kube.cloud.ovh.com general-worker-pool -o yaml
 2apiVersion: kube.cloud.ovh.com/v1alpha1
 3kind: NodePool
 4metadata:
 5  name: general-worker-pool
 6[...]
 7spec:
 8  desiredNodes: 4
 9  flavor: b2-15
10  maxNodes: 100
11  minNodes: 0
12[...]
13status:
14  availableNodes: 4
15  currentNodes: 4
16  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) :

 1kind: CustomResourceStateMetrics
 2spec:
 3  resources:
 4    - groupVersionKind: # Quelle ressource aller chercher
 5        group: kube.cloud.ovh.com
 6        kind: "NodePool"
 7        version: "v1alpha1"
 8      labelsFromPath:
 9        nodepool: [metadata, name]
10      metrics:
11        - name: "available_nodes_count"
12          help: "Number of available nodes in the nodepool"
13          each:
14            type: Gauge # Quel type de métrique
15            gauge:
16              path: [status] # Path où trouver l'information
17              valueFrom: [availableNodes]  # Valeur à récupérer
18        - name: "current_nodes_count"
19          help: "Number of current nodes in the nodepool"
20          each:
21            type: Gauge
22            gauge:
23              path: [status]
24              valueFrom: [currentNodes]
25        - name: "uptodate_nodes_count"
26          help: "Number of up-to-date nodes in the nodepool"
27          each:
28            type: Gauge
29            gauge:
30              path: [status]
31              valueFrom: [upToDateNodes]
32              path: [status, availableNodes] # Path où trouver l'information

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

1kube_customresource_available_nodes_count{customresource_group="kube.cloud.ovh.com",customresource_kind="NodePool",customresource_version="v1alpha1",nodepool="general-worker-pool"} 4
2kube_customresource_current_nodes_count{customresource_group="kube.cloud.ovh.com",customresource_kind="NodePool",customresource_version="v1alpha1",nodepool="general-worker-pool"} 4
3kube_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 :

 1# kubectl get vpa goldilocks-example -o yaml
 2apiVersion: autoscaling.k8s.io/v1
 3kind: VerticalPodAutoscaler
 4metadata:
 5  labels:
 6    creator: Fairwinds
 7    source: goldilocks
 8  name: goldilocks-example
 9spec:
10  targetRef:
11    apiVersion: apps/v1
12    kind: Deployment
13    name: example
14  updatePolicy:
15    updateMode: "Off"
16status:
17  conditions:
18  - lastTransitionTime: "2023-08-21T08:16:27Z"
19    status: "True"
20    type: RecommendationProvided
21  recommendation:
22    containerRecommendations:
23    - containerName: container
24      lowerBound:
25        cpu: 15m
26        memory: "1101864149"
27      target:
28        cpu: 15m
29        memory: "1238659775"
30      uncappedTarget:
31        cpu: 15m
32        memory: "1238659775"
33      upperBound:
34        cpu: 25m
35        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 :

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

     1# extract du fichier values.yaml
     2kube-state-metrics:
     3  customResourceState:
     4    enabled: true
     5    config:
     6      kind: CustomResourceStateMetrics
     7      spec:
     8        resources:
     9          - groupVersionKind:
    10              group: autoscaling.k8s.io
    11              kind: "VerticalPodAutoscaler"
    12              version: "v1"
    13            labelsFromPath:
    14              verticalpodautoscaler: [metadata, name]
    15              namespace: [metadata, namespace]
    16              target_api_version: [spec, targetRef, apiVersion]
    17              target_kind: [spec, targetRef, kind]
    18              target_name: [spec, targetRef, name]
    19            metrics:
    20              # Labels
    21              - name: "verticalpodautoscaler_labels"
    22                help: "VPA container recommendations. Kubernetes labels converted to Prometheus labels"
    23                each:
    24                  type: Info
    25                  info:
    26                    labelsFromPath:
    27                      name: [metadata, name]
    28              # Memory Information
    29              - name: "verticalpodautoscaler_status_recommendation_containerrecommendations_target"
    30                help: "VPA container recommendations for memory. Target resources the VerticalPodAutoscaler recommends for the container."
    31                each:
    32                  type: Gauge
    33                  gauge:
    34                    path: [status, recommendation, containerRecommendations]
    35                    valueFrom: [target, memory]
    36                    labelsFromPath:
    37                      container: [containerName]
    38                commonLabels:
    39                  resource: "memory"
    40                  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 :

 1$ helm upgrade prometheus prometheus-community/kube-prometheus-stack -f custom-values.yaml
 2Release "prometheus" has been upgraded. Happy Helming!
 3NAME: prometheus
 4LAST DEPLOYED: Wed Jan 10 09:26:19 2024
 5NAMESPACE: observability
 6STATUS: deployed
 7REVISION: 30
 8NOTES:
 9kube-prometheus-stack has been installed. Check its status by running:
10  kubectl --namespace observability get pods -l "release=prometheus"
11
12Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
13
14$ helm list
15NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
16prometheus      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 :

 1$ kubectl stern prom-kube-state-metrics-66dcd8b56b-snvz4
 2+ prom-kube-state-metrics-66dcd8b56b-snvz4 › kube-state-metrics
 3[...]
 4prom-kube-state-metrics-66dcd8b56b-snvz4 kube-state-metrics I0108 15:06:07.348917       1 wrapper.go:120] "Starting kube-state-metrics"
 5prom-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"
 6prom-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"
 7[...]
 8prom-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"]
 9prom-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"
10prom-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 :

1$ kubectl port-forward svc/prom-kube-state-metrics 8080:8080
2 
3$ curl localhost:8080/metrics
4[...]
5# HELP kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target VPA container recommendations for memory. Target resources the VerticalPodAutoscaler recommends for the container.
6# TYPE kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_target gauge
7kube_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
8kube_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
9[...]

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 :