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