
Di norma i deployment Kubernetes vengono scalati in base a metriche come consumo di CPU o memoria, ma in alcuni casi è necessario scalare su metriche esterne. In questo post vi guido passo passo nella configurazione dell'autoscaling con Horizontal Pod Autoscaler (HPA) basato su una qualsiasi metrica Stackdriver: nello specifico useremo le Request Per Second di un Load Balancer HTTP/S di Google Cloud.

Autoscaling dell'Horizontal Pod Autoscaler di Kubernetes con metriche Stackdriver
Si comincia!
Per prima cosa creiamo un nuovo cluster 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-autorepairAttenzione al parametro `enable-cloud-monitoring`: è ciò che ci permetterà di leggere le metriche di Stackdriver Monitoring.
Deploy del Custom Metrics Stackdriver Adapter
Il custom metrics adapter si occupa di importare le metriche Stackdriver nelle API di Kubernetes, così l'HPA potrà consultarle e agire di conseguenza. Trovate maggiori dettagli più avanti, nella sezione di troubleshooting.
Per dare agli oggetti GKE l'accesso alle metriche memorizzate in Stackdriver, occorre installare il Custom Metrics Stackdriver Adapter nel cluster.
Per poter eseguire il Custom Metrics Adapter, dovete prima abilitare il vostro utente alla creazione dei ruoli di autorizzazione richiesti, lanciando questo comando:
kubectl create clusterrolebinding cluster-admin-binding \--clusterrole cluster-admin \--user "$(gcloud config get-value account)"Ora installiamo l'adapter vero e proprio, che ci consentirà di leggere le metriche da Stackdriver:
kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter.yamlCreare un Deployment
Adesso facciamo il deploy di una semplice applicazione nginx, che in seguito verrà scalata in base alle RPS misurate dal Load Balancer HTTP/S.
Create questo file: 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: nginxOra applichiamolo:
kubectl apply -f deployment.yamlCreare un Ingress LoadBalancer
Create il file di ingress: ingress.yaml
apiVersion: extensions/v1beta1kind: Ingressmetadata: name: basic-ingressspec: backend: serviceName: nginx servicePort: 80E applicate l'ingress:
kubectl apply -f ingress.yamlCreare l'oggetto HorizontalPodAutoscaler
È qui che succede la magia.
Useremo una metrica esterna*, indicando come metricName:
loadbalancing.googleapis.com|https|Nota: l'elenco completo delle metriche Stackdriver lo trovate qui, oppure potete usare il Metrics Explorer.
Conviene anche definire un metricSelector, in modo da limitare la lettura alle sole metriche del nostro specifico load balancer.
Individuiamo la forwarding rule del nostro 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--ffd629d77b6630deA questo punto possiamo aggiungere il match della label alla nostra configurazione (notate la label "forwarding_rule_name"):
metricSelector: matchLabels: resource.labels.forwarding_rule_name: k8s-fw-default-basic-ingress--ffd629d77b6630deIl file finale sarà questo: 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: nginxNotate l'uso di targetAverageValue: indica quanta parte del valore totale della metrica può gestire ciascuna replica. È utile con metriche che descrivono lavoro o risorse ripartibili tra le repliche; nel nostro caso ogni replica può gestire una singola RPS (cioè 1). Il valore va naturalmente adattato alle vostre esigenze.
Mettiamo tutto alla prova
Cominciamo generando traffico verso il nostro load balancer.
Come si vede dal comando precedente:
kubectl describe ingress basic-ingressL'indirizzo IP pubblico del nostro Ingress è 35.190.3.165.
Ora bombardiamo l'endpoint 🥊 con qualche richiesta:
while true ; do curl -Ss -k --write-out '%{http_code}\n' --output /dev/null http://35.190.3.165/ ; doneVediamo se il nostro HorizontalPodAutoscaler reagisce:
kubectl describe hpa nginx-hpaAll'inizio potreste vedere qualche warning, perché la metrica non è ancora popolata; dopo qualche minuto, però, la sezione Metrics si riempie:
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: 5E nella sezione "Events" troviamo:
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 targetDecollo riuscito! 🚀
Troubleshooting
- Un modo semplice per verificare se la metrica viene effettivamente importata nelle external metrics API di Kubernetes è esplorare l'API a mano. Vi servirà anche per controllare di aver impostato correttamente il metricSelector.
Per prima cosa, avviamo il proxy di Kubernetes:
kubectl proxy --port=8080Poi possiamo accedere da localhost:
http://localhost:8080/apis/external.metrics.k8s.io/v1beta1/namespaces/default/loadbalancing.googleapis.com%7Chttps%7Crequest_countEcco un estratto del risultato:
{ "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. Un altro modo per capire come si comporta l'adapter è leggerne i log; iniziamo elencando i pod custom-metrics:
$ kubectl get pods -n custom-metricsNAME READYcustom-metrics-stackdriver-adapter-c4d98dc54-2n4jz 1/1Infine, leggiamo i log:
$ 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...Da questi log si ricavano molte informazioni preziose, soprattutto in caso di messaggi di errore.
Conclusione
Sfruttare metriche esterne come quelle raccolte e archiviate da Stackdriver è semplice e immediato. Allo stesso modo potete utilizzare anche le vostre custom metrics pubblicate su Stackdriver tramite la Monitoring API.
Ho creato un repository GitHub con tutte le risorse usate per questo post: lo trovate qui.
Volete altri articoli? Date un'occhiata al nostro blog oppure seguite Eran su Twitter.