Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Autoescalado del HPA en Kubernetes con RPS del HTTP/S Load Balancer de Google vía Stackdriver

By Eran ChetzroniNov 5, 20185 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

1 ce4zye4gpmytsbthtlntwq

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.

1 ce4zye4gpmytsbthtlntwq

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-autorepair

Fí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.yaml

Crear 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/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
selector:
app: nginx

Ahora lo desplegamos:

kubectl apply -f deployment.yaml

Crear el Ingress del LoadBalancer

Crea el archivo del ingress: ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: basic-ingress
spec:
backend:
serviceName: nginx
servicePort: 80

Y aplica el ingress:

kubectl apply -f ingress.yaml

Crear 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-ingress
Name: basic-ingress
Namespace: default
Address: 35.190.3.165
Default 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--ffd629d77b6630de

Ya 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--ffd629d77b6630de

El archivo final queda así: hpa.yaml

apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: nginx
spec:
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: nginx

Fí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-ingress

La 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/ ; done

Veamos si nuestro HorizontalPodAutoscaler reacciona:

kubectl describe hpa nginx-hpa

Al 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-hpa
Namespace: default
Labels: <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 +0200
Reference: Deployment/nginx
Metrics: ( current / target )
"loadbalancing.googleapis.com|https|request_count" (target average value): 1034m / 1
Min replicas: 1
Max replicas: 5

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

  1. 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=8080

Y luego entramos desde nuestro localhost:

http://localhost:8080/apis/external.metrics.k8s.io/v1beta1/namespaces/default/loadbalancing.googleapis.com%7Chttps%7Crequest_count

Este 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-metrics
NAME READY
custom-metrics-stackdriver-adapter-c4d98dc54-2n4jz 1/1

Y 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.