
Casi siempre los deployments de Kubernetes se escalan según métricas como el consumo de CPU o memoria, pero a veces hay que hacerlo con métricas externas. En este post te muestro paso a paso cómo configurar el autoescalado del Horizontal Pod Autoscaler (HPA) con cualquier métrica de Stackdriver; en concreto, vamos a usar los Requests Per Second de un HTTP/S Load Balancer de Google Cloud.

Autoescalado del Horizontal Pod Autoscaler de Kubernetes con métricas de Stackdriver
¡Manos a la obra!
Primero vamos a crear un nuevo cluster de Google Kubernetes Engine (GKE):
gcloud beta container clusters create "hpa-with-stackdriver-metrics" --zone "us-central1-a" \--username "admin" \--cluster-version "1.10.7-gke.6" \--machine-type "n1-standard-1" \--image-type "COS" \--disk-type "pd-standard" \--disk-size "100" --scopes \ "https://www.googleapis.com/auth/devstorage.read_only","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/monitoring","https://www.googleapis.com/auth/servicecontrol","https://www.googleapis.com/auth/service.management.readonly","https://www.googleapis.com/auth/trace.append"--num-nodes "3" \--enable-cloud-logging \--enable-cloud-monitoring \--addons HorizontalPodAutoscaling,HttpLoadBalancing \--enable-autoupgrade --enable-autorepairFíjate en el flag `enable-cloud-monitoring`: es el que nos va a permitir leer las métricas de Stackdriver Monitoring.
Desplegar el Custom Metrics Stackdriver Adapter
El custom metrics adapter se encarga de importar las métricas de Stackdriver a la API de Kubernetes; así el HPA puede consumirlas y actuar en consecuencia. Encontrarás más detalles en la sección de troubleshooting más abajo.
Para que los objetos de GKE puedan acceder a las métricas almacenadas en Stackdriver, hay que desplegar el Custom Metrics Stackdriver Adapter en tu cluster.
Para poder ejecutar el Custom Metrics Adapter, primero tienes que darle a tu usuario permisos para crear los roles de autorización necesarios. Hazlo con este comando:
kubectl create clusterrolebinding cluster-admin-binding \--clusterrole cluster-admin \--user "$(gcloud config get-value account)"Y ahora desplegamos el adapter, que es lo que nos va a permitir leer las métricas desde Stackdriver:
kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter.yamlCrear un Deployment
Ahora vamos a desplegar una aplicación nginx sencilla, que más adelante escalaremos según los RPS medidos por el HTTP/S Load Balancer.
Crea este archivo: deployment.yaml
apiVersion: apps/v1kind: Deploymentmetadata: name: nginxspec: selector: matchLabels: app: nginx replicas: 1 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.8 ports: - containerPort: 80---apiVersion: v1kind: Servicemetadata: name: nginx labels: app: nginxspec: type: NodePort ports: - port: 80 protocol: TCP selector: app: nginxAhora lo desplegamos:
kubectl apply -f deployment.yamlCrear el Ingress del LoadBalancer
Crea el archivo del ingress: ingress.yaml
apiVersion: extensions/v1beta1kind: Ingressmetadata: name: basic-ingressspec: backend: serviceName: nginx servicePort: 80Y aplica el ingress:
kubectl apply -f ingress.yamlCrear el objeto HorizontalPodAutoscaler
Aquí es donde ocurre la magia.
Vamos a usar una métrica externa*, con metricName:
loadbalancing.googleapis.com|https|Nota: puedes consultar el listado completo de métricas de Stackdriver aquí o usar el Metrics Explorer.
También conviene usar un metricSelector para asegurarnos de que solo tomamos las métricas de nuestro load balancer en concreto.
Vamos a buscar la forwarding rule de nuestro LB:
$ kubectl describe ingress basic-ingressName: basic-ingressNamespace: defaultAddress: 35.190.3.165Default backend: nginx:80 (10.48.2.11:80)Rules: Host Path Backends ---- ---- -------- * * nginx:80 (10.48.2.11:80)Annotations: backends: {"k8s-be-32432--ffd629d77b6630de":"HEALTHY"} forwarding-rule: k8s-fw-default-basic-ingress--ffd629d77b6630de target-proxy: k8s-tp-default-basic-ingress--ffd629d77b6630de url-map: k8s-um-default-basic-ingress--ffd629d77b6630deYa podemos agregar el match de label a nuestra config (fíjate en la label "forwarding_rule_name"):
metricSelector: matchLabels: resource.labels.forwarding_rule_name: k8s-fw-default-basic-ingress--ffd629d77b6630deEl archivo final queda así: hpa.yaml
apiVersion: autoscaling/v2beta1kind: HorizontalPodAutoscalermetadata: name: nginxspec: minReplicas: 1 maxReplicas: 5 metrics: - external: metricName: loadbalancing.googleapis.com|https|request_count metricSelector: matchLabels: resource.labels.forwarding_rule_name: k8s-fw-default-basic-ingress--ffd629d77b6630de targetAverageValue: "1" type: External scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginxFíjate en que usamos targetAverageValue: este parámetro indica cuánto del valor total de la métrica puede manejar cada réplica. Es útil cuando se trata de métricas que describen un trabajo o un recurso que se puede repartir entre réplicas; en nuestro caso, cada réplica puede manejar un único (es decir, 1) RPS. Por supuesto, ajústalo según tus necesidades.
Vamos a probarlo todo
Empecemos enviando tráfico a nuestro load balancer.
Como vimos con el comando anterior:
kubectl describe ingress basic-ingressLa IP pública de nuestro Ingress es 35.190.3.165.
Ahora le mandamos algunos requests a ese endpoint 🥊:
while true ; do curl -Ss -k --write-out '%{http_code}\n' --output /dev/null http://35.190.3.165/ ; doneVeamos si nuestro HorizontalPodAutoscaler reacciona:
kubectl describe hpa nginx-hpaAl principio puede que veas algunos warnings porque la métrica todavía no tiene datos, pero después de unos minutos verás que la sección Metrics se llena:
Name: nginx-hpaNamespace: defaultLabels: <none>Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"autoscaling/v2beta1","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"name":"nginx-hpa","namespace":"default"},"spec":{"ma...CreationTimestamp: Wed, 31 Oct 2018 18:18:28 +0200Reference: Deployment/nginxMetrics: ( current / target )"loadbalancing.googleapis.com|https|request_count" (target average value): 1034m / 1Min replicas: 1Max replicas: 5Y en la sección "Events" vemos:
Events:Type Reason Age From Message...Normal SuccessfulRescale 2m horizontal-pod-autoscaler New size: 2; reason: external metric loadbalancing.googleapis.com|https|request_count(&LabelSelector{MatchLabels:map[string]string{resource.labels.forwarding_rule_name: k8s-fw-default-basic-ingress--ffd629d77b6630de,},MatchExpressions:[],}) above target¡Despegamos! 🚀
Troubleshooting:
- Una forma fácil de comprobar si la métrica se está importando a la external metrics api de Kubernetes es navegar la api a mano. También te servirá para verificar si configuraste bien el metricSelector.
Lo primero, levantamos el proxy de Kubernetes:
kubectl proxy --port=8080Y luego entramos desde nuestro localhost:
http://localhost:8080/apis/external.metrics.k8s.io/v1beta1/namespaces/default/loadbalancing.googleapis.com%7Chttps%7Crequest_countEste es un extracto del resultado:
{ "kind": "ExternalMetricValueList", "apiVersion": "external.metrics.k8s.io/v1beta1", "metadata": { "selfLink": "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/loadbalancing.googleapis.com%7Chttps%7Crequest_count" }, "items": [\ {\ "metricName": "loadbalancing.googleapis.com|https|request_count",\ "metricLabels": {\ "metric.labels.cache_result": "DISABLED",\ "resource.labels.backend_target_type": "BACKEND_SERVICE",\ "resource.labels.backend_name": "k8s-ig--ffd629d77b6630de",\ ...\ "resource.labels.forwarding_rule_name": "k8s-fw-default-basic-ingress--ffd629d77b6630de",\ ...\ },\ "timestamp": "2018-11-01T08:41:30Z",\ "value": "2433m"\ }\ ]}¡Voilá!
2. Otra forma de ver cómo se comporta el adapter es revisar sus logs. Primero listamos los pods de custom-metrics:
$ kubectl get pods -n custom-metricsNAME READYcustom-metrics-stackdriver-adapter-c4d98dc54-2n4jz 1/1Y por último revisamos los logs:
$ kubectl logs custom-metrics-stackdriver-adapter-c4d98dc54-2n4jz -n custom-metrics...I1104 06:42:11.125627 1 trace.go:76] Trace[1192308782]: "List /apis/external.metrics.k8s.io/v1beta1/namespaces/default/loadbalancing.googleapis.com|https|request_count" (started: 2018-11-04 06:42:08.155209905 +0000 UTC m=+311951.293335726) (total time: 2.970372027s):Trace[1192308782]: [2.97027864s] [2.970185564s] Listing from storage done...De esos logs se saca mucha información valiosa, sobre todo si hay mensajes de error.
Conclusión
Usar métricas externas como las que recopila y almacena Stackdriver es bastante directo y sencillo de implementar. De forma similar, también puedes usar tus propias métricas personalizadas publicándolas en Stackdriver mediante la Monitoring API.
Armé un repositorio en GitHub con los recursos que usé para este post aquí.
¿Quieres más historias? Pásate por nuestro blog o sigue a Eran en Twitter.