Kube-state-metrics et les Vertical Pod Autoscalers
Utilisez kube-state-metrics pour exploiter les métriques des VPAs
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_createdkube_job_completekube_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: 4On 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'informationCela 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"} 4Sur 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.0On 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_labelskube_verticalpodautoscaler_status_recommendation_containerrecommendations_target->kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_targetkube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound->kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerboundkube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound->kube_customresource_verticalpodautoscaler_status_recommendation_containerrecommendations_upperboundkube_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 !

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 :