Autoscaling des pods via des métriques personnalisées dans GKE
Découvrez comment rendre des métriques personnalisées disponibles dans le service Google Cloud Monitoring et faire en sorte que l'Horizontal Pod Autoscaler (HPA) utilise ces métriques pour la mise à l'échelle de nos pods.
Aperçu du cas d'utilisation
L'exemple portera sur la mise à l'échelle des pods d'une application « Selenium Grid » en fonction de la taille de la « file d'attente des sessions ».
L'application « Selenium Grid » est essentiellement composée de deux composants :
- hub : reçoit les demandes des clients et les transmet aux nœuds disponibles
- nœuds : sont enregistrés auprès du Hub et traitent les demandes des clients transférées
Lorsque tous les pods « nœuds » sont occupés, les demandes des clients sont envoyées dans la « file d'attente des sessions ».
Notre objectif est de faire en sorte que l'HorizontalPodAutoscaler (HPA) ajoute plus de pods « nœuds » en fonction de la taille de la « file d'attente des sessions ».
Voici le manifeste Kubernetes utilisé pour déployer l'application « Selenium Grid », basé sur des exemples de manifestes disponibles ici : exemples-selenium-kubernetes.
# Selenium nodes
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-node-firefox
namespace: default
labels:
app: selenium-node-firefox
spec:
replicas: 1
selector:
matchLabels:
app: selenium-node-firefox
template:
metadata:
labels:
app: selenium-node-firefox
spec:
volumes:
- name: dshm
emptyDir:
medium: Memory
containers:
- name: selenium-node-firefox
image: selenium/node-firefox:4.18.0-20240220
volumeMounts:
- mountPath: /dev/shm
name: dshm
env:
- name: SE_EVENT_BUS_HOST
value: "selenium-hub"
- name: SE_EVENT_BUS_SUBSCRIBE_PORT
value: "4443"
- name: SE_EVENT_BUS_PUBLISH_PORT
value: "4442"
- name: NODE_MAX_INSTANCES
value: "1"
- name: NODE_MAX_SESSION
value: "1"
resources:
limits:
memory: "250Mi"
cpu: "0.2"
---
# Selenium Hub
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-hub
namespace: default
labels:
app: selenium-hub
spec:
replicas: 1
selector:
matchLabels:
app: selenium-hub
template:
metadata:
labels:
app: selenium-hub
spec:
containers:
- name: selenium-hub
image: selenium/hub:4.18.0-20240220
ports:
- containerPort: 4444
- containerPort: 4443
- containerPort: 4442
resources:
limits:
memory: "200Mi"
cpu: ".2"
livenessProbe:
httpGet:
path: /wd/hub/status
port: 4444
initialDelaySeconds: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /wd/hub/status
port: 4444
initialDelaySeconds: 30
timeoutSeconds: 5
---
# Selenium Hub service
apiVersion: v1
kind: Service
metadata:
name: selenium-hub
namespace: default
labels:
app: selenium-hub
spec:
ports:
- port: 4444
targetPort: 4444
name: port0
- port: 4443
targetPort: 4443
name: port1
- port: 4442
targetPort: 4442
name: port2
selector:
app: selenium-hub
type: ClusterIP
Exposez les métriques de l'application via l'exporteur Prometheus
Nous devons d’abord nous assurer que les métriques dont nous avons besoin sont disponibles dans le format Prometheus via un point de terminaison HTTP.
Malheureusement, ce n'est pas déjà le cas pour notre application « Selenium Grid ». La métrique dont nous avons besoin n'est pas disponible au format requis. Il nous reste donc une étape supplémentaire à effectuer avant de pouvoir la récupérer et l'intégrer au service « Google Cloud Monitoring » (anciennement « Stackdriver »).
Heureusement, quelqu'un a déjà créé un Exporteur de métriques Prometheus pour « Selenium Grid ». Cet exporteur interroge l'API « Selenium Grid », crée des métriques utiles à partir des réponses et les exposent dans le format Prometheus via un point de terminaison HTTP. Pour en savoir plus sur les exporteurs Prometheus, consultez Créer des exporteurs Prometheus.
Déployons l'exporteur Prometheus pour exposer les métriques « Selenium Grid » au format requis. Voici les manifestes Kubernetes correspondants :
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-grid-metrics-exporter
namespace: default
labels:
app: selenium-grid-metrics-exporter
spec:
replicas: 1
selector:
matchLabels:
app: selenium-grid-metrics-exporter
template:
metadata:
labels:
app: selenium-grid-metrics-exporter
spec:
containers:
- name: selenium-grid-metrics-exporter
image: gmichels/selenium-grid-exporter:v1.0.0
ports:
- containerPort: 8080
command:
- /bin/sh
- -c
- python ./exporter.py --grid-url http://selenium-hub:4444 --publish-interval 15 -p 8080
---
apiVersion: v1
kind: Service
metadata:
name: selenium-grid-metrics-exporter
namespace: default
labels:
app: selenium-grid-metrics-exporter
spec:
ports:
- port: 8080
targetPort: 8080
name: http
selector:
app: selenium-grid-metrics-exporter
type: ClusterIP
Après avoir créé les objets Deployment et Service précédents pour l'exporteur Prometheus, interrogeons le service pour voir si la métrique concernant la taille de la file d'attente des sessions est disponible :
# Create a proxy from local machine to the exporter service
$ kubectl port-forward svc/selenium-grid-metrics-exporter --address 172.25.22.210 8080:8080 &
# Query the exporter service through the local proxy
$ curl http://172.25.22.210:8080/metrics
Handling connection for 8080
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 112.0
python_gc_objects_collected_total{generation="1"} 266.0
python_gc_objects_collected_total{generation="2"} 0.0
(...)
# HELP selenium_grid_total_slots Total number of slots in the grid
# TYPE selenium_grid_total_slots gauge
selenium_grid_total_slots 1.0
# HELP selenium_grid_node_count Number of nodes in grid
# TYPE selenium_grid_node_count gauge
selenium_grid_node_count 1.0
# HELP selenium_grid_session_count Number of running sessions
# TYPE selenium_grid_session_count gauge
selenium_grid_session_count 0.0
# HELP selenium_grid_session_queue_size Number of queued sessions
# TYPE selenium_grid_session_queue_size gauge
selenium_grid_session_queue_size 0.0
# HELP selenium_node_slot_count Total number of node slots
# TYPE selenium_node_slot_count gauge
selenium_node_slot_count{deployment="selenium-node-chrome",node="chrome"} 0.0
selenium_node_slot_count{deployment="selenium-node-firefox",node="firefox"} 1.0
# HELP selenium_node_session_count Total number of node slots
# TYPE selenium_node_session_count gauge
selenium_node_session_count{deployment="selenium-node-chrome",node="chrome"} 0.0
selenium_node_session_count{deployment="selenium-node-firefox",node="firefox"} 0.0
# HELP selenium_node_usage_percent % of used node slots
# TYPE selenium_node_usage_percent gauge
selenium_node_usage_percent{deployment="selenium-node-chrome",node="chrome"} 0.0
selenium_node_usage_percent{deployment="selenium-node-firefox",node="firefox"} 0.0
Nous voyons un ensemble de métriques utiles pour l'application « Selenium Grid », exposées au format Prometheusrequis. Très bien.
La métrique relative au nombre de sessions en file d'attente dont nous avons besoin pour la mise à l'échelle automatique des pods de l'application est également disponible sous le nom « selenium_grid_session_queue_size ».
Ensuite, nous allons rendre la métrique « selenium_grid_session_queue_size » disponible dans le service « Google Cloud Monitoring », pour une utilisation ultérieure par le HPA.
Envoyer les métriques des exporteurs Prometheus à Cloud Monitoring
Déployer Prometheus-to-sd afin de collecter les métriques Prometheus exportées et les envoyer au service « Google Cloud Monitoring » (anciennement « Stackdriver »).
Voici le manifeste Kubernetes pour cela :
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-grid-metrics-prometheus-to-sd
namespace: default
labels:
app: selenium-grid-metrics-prometheus-to-sd
spec:
replicas: 1
selector:
matchLabels:
app: selenium-grid-metrics-prometheus-to-sd
template:
metadata:
labels:
app: selenium-grid-metrics-prometheus-to-sd
spec:
containers:
- name: selenium-grid-metrics-prometheus-to-sd
image: gcr.io/google-containers/prometheus-to-sd:v0.9.2
ports:
- name: profiler
containerPort: 6060
command:
- /monitor
# Scrape and export intervals are implemented in versions of prometheus-to-sd > v0.9.0
- --scrape-interval=20s # how often in seconds metrics are pulled from Grid Hub service
- --export-interval=30s # how often in seconds metrics are pushed to Stackdriver service
- --stackdriver-prefix=custom.googleapis.com
- --source=selenium-grid-metrics-exporter:http://selenium-grid-metrics-exporter:8080
- --pod-id=$(POD_NAME)
- --namespace-id=$(POD_NAMESPACE)
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
Jetons un œil aux statuts des pods :
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
selenium-grid-metrics-exporter-57577d8869-kkfhz 1/1 Running 0 57m
selenium-grid-metrics-prometheus-to-sd-9d54fc8d7-8n4gs 1/1 Running 0 2m22s
selenium-hub-6787dc9b7d-hcsht 1/1 Running 0 84m
selenium-node-firefox-85d5c7b75b-b7vmb 1/1 Running 0 72m
$ kubectl logs -f pods/selenium-grid-metrics-prometheus-to-sd-9d54fc8d7-8n4gs
(...)
I0221 13:39:45.551781 1 main.go:182] Taking source configs from flags
I0221 13:39:45.551811 1 main.go:184] Taking source configs from kubernetes api server
I0221 13:39:45.551906 1 main.go:124] Built the following source configs: [0xc0002d4a90]
I0221 13:39:45.551987 1 main.go:193] Running prometheus-to-sd, monitored target is selenium-grid-metrics-exporter http://selenium-grid-metrics-exporter:8080
Tout semble fonctionner. Si vous obtenez une erreur d'autorisation 403 à cette étape, vérifiez d'abord deux points :
-
si workload identity est activé sur le pool de nœuds, assurez-vous que le compte de service Kubernetes associé aux pods exécutant le conteneur Prometheus-to-sd est lié à un compte de service IAM disposant au moins de l'autorisation « monitoring.timeSeries.create ». Pour savoir comment procéder, consultez Configuration de workload identity.
-
si workload identity n'est pas activé sur le pool de nœuds, assurez-vous que la fonctionnalité « Cloud Monitoring » est activée sur le cluster GKE. L'activation de cette fonctionnalité ajoutera la portée OAuth (OAuth scope)
https://www.googleapis.com/auth/monitoringpour les pools de nœuds nouvellement créés, permettant un accès complet en lecture et en écriture à l'API « Cloud Monitoring ». Consultez Portées d'accès OAuth GKE et Toutes les portées d'accès OAuth de l'API Google pour plus de détails. Migrez vos charges de travail vers le pool de nœuds nouvellement créé après avoir activé la fonctionnalité « Cloud Monitoring ». La commande suivante :
gcloud container clusters describe <cluster-name> --zone <cluster-zone>
pourrait être utilisée pour voir les portées OAuth des pools de nœuds du cluster en recherchant « oauthScopes » dans le résultat.
La métrique « selenium_grid_session_queue_size » devrait désormais être présente dans le service « Google Cloud Monitoring ». Vérifions en accédant à l'Explorateur de métriques Google Cloud :


Notez que le conteneur
gcr.io/google-containers/prometheus-to-sdpourrait également être déployé en tant que conteneur sidecar à l'intérieur des pods de l'exporteur Prometheus exposant les métriques personnalisées qui nous intéressent. Pour en savoir plus sur l'export de métriques vers le service Google Cloud Monitoring, jetez un œil à Exemples-Prometheus-vers-stackdriver.
S'assurer que le HPA peut interroger des métriques personnalisées/externes
Maintenant que la métrique dont nous avons besoin est disponible dans le service « Google Cloud Monitoring », nous devons nous assurer que les objets HorizontalPodAutoscaler (HPA) sont capables de la lire.
Pour cela, nous devons étendre le service d'API Kubernetes, afin de créer des API qui seront utilisées par le HPA, et ensuite pouvoir obtenir les métriques personnalisées/externes disponibles dans le service « Google Cloud Monitoring ».
Pour y parvenir, nous devons déployer un adaptateur de métriques personnalisées/externes au sein du cluster GKE. Voir adaptateur de métriques personnalisées stackdriver pour en savoir plus.
Voici la commande utilisée pour déployer l'adaptateur de métriques personnalisées/externes :
# If the resource type of the metrics we want to get from the new metrics APIs
# uses the new resource model: k8s_pod, k8s_node
$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
# If the resource type of the metrics we want to get from the new metrics APIs
# uses the legacy resource model: gke_container
$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter.yaml
# Result
namespace/custom-metrics created
serviceaccount/custom-metrics-stackdriver-adapter created
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/custom-metrics-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics-resource-reader created
deployment.apps/custom-metrics-stackdriver-adapter created
service/custom-metrics-stackdriver-adapter created
apiservice.apiregistration.k8s.io/v1beta1.custom.metrics.k8s.io created
apiservice.apiregistration.k8s.io/v1beta2.custom.metrics.k8s.io created
apiservice.apiregistration.k8s.io/v1beta1.external.metrics.k8s.io created
Warning: resource clusterroles/external-metrics-reader is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
clusterrole.rbac.authorization.k8s.io/external-metrics-reader configured
clusterrolebinding.rbac.authorization.k8s.io/external-metrics-reader created
Après cela, deux nouvelles API de métriques seront disponibles avec les groupes d'API suivants : « external.metrics.k8s.io » et « custom.metrics.k8s.io ».
Jetons un œil aux journaux de l’adaptateur de métriques personnalisées/externes :
$ kubectl get all -n custom-metrics
NAME READY STATUS RESTARTS AGE
pod/custom-metrics-stackdriver-adapter-df7d6cfcd-tkp45 1/1 Running 0 3m21s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/custom-metrics-stackdriver-adapter ClusterIP 10.1.0.208 <none> 443/TCP 3m20s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/custom-metrics-stackdriver-adapter 1/1 1 1 3m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/custom-metrics-stackdriver-adapter-df7d6cfcd 1 1 1 3m22s
$ kubectl logs -f pods/custom-metrics-stackdriver-adapter-df7d6cfcd-tkp45 -n custom-metrics
I0222 09:38:55.812226 1 adapter.go:200] serverOptions: {false true true false false false false}
I0222 09:38:55.812362 1 adapter.go:210] ListFullCustomMetrics is disabled, which would only list 1 metric resource to reduce memory usage. Add --list-full-custom-metrics to list full metric resources for debugging.
I0222 09:38:59.132069 1 serving.go:341] Generated self-signed cert (apiserver.local.config/certificates/apiserver.crt, apiserver.local.config/certificates/apiserver.key)
I0222 09:39:01.415941 1 secure_serving.go:256] Serving securely on [::]:443
I0222 09:39:01.416012 1 requestheader_controller.go:169] Starting RequestHeaderAuthRequestController
I0222 09:39:01.416025 1 shared_informer.go:240] Waiting for caches to sync for RequestHeaderAuthRequestController
I0222 09:39:01.416068 1 dynamic_serving_content.go:129] "Starting controller" name="serving-cert::apiserver.local.config/certificates/apiserver.crt::apiserver.local.config/certificates/apiserver.key"
I0222 09:39:01.416127 1 tlsconfig.go:240] "Starting DynamicServingCertificateController"
I0222 09:39:01.416253 1 configmap_cafile_content.go:201] "Starting controller" name="client-ca::kube-system::extension-apiserver-authentication::client-ca-file"
I0222 09:39:01.416259 1 shared_informer.go:240] Waiting for caches to sync for client-ca::kube-system::extension-apiserver-authentication::client-ca-file
I0222 09:39:01.416273 1 configmap_cafile_content.go:201] "Starting controller" name="client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file"
I0222 09:39:01.416278 1 shared_informer.go:240] Waiting for caches to sync for client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file
I0222 09:39:01.516235 1 shared_informer.go:247] Caches are synced for RequestHeaderAuthRequestController
I0222 09:39:01.516354 1 shared_informer.go:247] Caches are synced for client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file
I0222 09:39:01.536396 1 shared_informer.go:247] Caches are synced for client-ca::kube-system::extension-apiserver-authentication::client-ca-file
Il semble que tout fonctionne correctement. Nous pouvons voir que la métrique dont nous avons besoin est désormais disponible via les nouvelles API de métriques en faisant :
$ kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size" | jq
{
"kind": "ExternalMetricValueList",
"apiVersion": "external.metrics.k8s.io/v1beta1",
"metadata": {},
"items": [
{
"metricName": "custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size",
"metricLabels": {
"resource.labels.cluster_name": "mygkecluster",
"resource.labels.container_name": "",
"resource.labels.instance_id": "gke-mygkecluster-main-486d49bd-w6gb",
"resource.labels.namespace_id": "default",
"resource.labels.pod_id": "selenium-grid-metrics-prometheus-to-sd-9d54fc8d7-twctc",
"resource.labels.project_id": "wise-bulk-410910",
"resource.labels.zone": "us-central1-c",
"resource.type": "gke_container"
},
"timestamp": "2024-02-22T10:45:16Z",
"value": "5"
}
]
}
# or
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq | grep -i "selenium_grid_session_queue_size"
Mise à l'échelle automatique à l'aide de mesures personnalisées/externes
Nous pouvons maintenant configurer le HPA pour utiliser la métrique personnalisée/externe pour la mise à l'échelle des pods « Nœuds Selenium Grid ». Voici le manifeste pour la création de la ressource HPA .
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: selenium-grid-nodes
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: selenium-node-firefox
minReplicas: 1
maxReplicas: 3
metrics:
- type: External
external:
metric:
name: custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size
target:
type: Value
value:
Après avoir déployé le HPA, nous devrions voir la taille de la file d'attente des sessions dans la colonne « TARGET » comme indiqué ci-dessous, ce qui signifie que le HPA est désormais capable d'utiliser la métrique personnalisée « selenium_grid_session_queue_size » pour la mise à l'échelle automatique des pods « nœuds » de l'application « Selenium Grid ».
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
selenium-grid-nodes Deployment/selenium-node-firefox 10/3 1 3 3 58m
Voici ce que nous voyons lorsque le HPA n'est pas en mesure d'obtenir la valeur de la métrique concernant la taille de la file d'attente des sessions :
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
selenium-grid-nodes Deployment/selenium-node-firefox <unknown>/3 1 3 0 16s
$ kubectl describe hpa
Name: selenium-grid-nodes
Namespace: default
(...)
Reference: Deployment/selenium-node-firefox
Metrics: ( current / target )
"custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size" (target value): <unknown> / 3
Min replicas: 1
Max replicas: 3
Deployment pods: 1 current / 1 desired
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ReadyForNewScale recommended size matches current size
ScalingActive False FailedGetExternalMetric the HPA was unable to compute the replica count: unable to get external metric default/custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size/nil: unable to fetch metrics from external metrics API: the server could not find the requested resource (get custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size.external.metrics.k8s.io)
ScalingLimited False DesiredWithinRange the desired count is within the acceptable range
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedGetExternalMetric 6s (x2 over 21s) horizontal-pod-autoscaler unable to get external metric default/custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size/nil: unable to fetch metrics from external metrics API: the server could not find the requested resource (get custom.googleapis.com|selenium-grid-metrics-exporter|selenium_grid_session_queue_size.external.metrics.k8s.io)
Réglage du HPA
Pour personnaliser les comportements de « mise à l'échelle verticale » (scale in) et de « mise à l'échelle horizontale » (scale out) pour l' HPA, voici la section de la référence de l'API hpa-v2 à ce sujet.
La fréquence à laquelle le HPA extrait les métriques des serveurs de métriques est un paramètre de kube-controller-manager ('--horizontal-pod-autoscaler-sync-period') et est défini par défaut sur « 15 s ».
Pour changer l'intervalle de scraping et d'export du composant Prometheus-to-sd, les paramètres « --scrape-interval » et « --export-interval » peuvent être utilisés avec la commande « monitor ». Voir Intervalle-de-scraping-vs-intervalle d-export-prometheus-vers-sd et fichier-go-principal-prometheus-vers-sd pour en savoir plus.