Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Autoscaling do HPA no K8s com RPS do Google HTTP/S Load Balancer via Stackdriver

By Eran ChetzroniNov 5, 20185 min read

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

1 ce4zye4gpmytsbthtlntwq

Na maioria das vezes, escalamos nossos deployments do Kubernetes com base em métricas como consumo de CPU ou memória, mas às vezes é preciso escalar a partir de métricas externas. Neste post, vou te mostrar o passo a passo para configurar o autoscaling do Horizontal Pod Autoscaler (HPA) usando qualquer métrica do Stackdriver — mais especificamente, o Request Per Second de um Google Cloud HTTP/S Load Balancer.

1 ce4zye4gpmytsbthtlntwq

Autoscaling do Horizontal Pod Autoscaler do Kubernetes com métricas do Stackdriver

Bora lá!

Primeiro, vamos criar um cluster novo no 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

Repare na flag `enable-cloud-monitoring` — é ela que vai permitir a leitura das métricas do Stackdriver Monitoring.

Faça o deploy do Custom Metrics Stackdriver Adapter

O custom metrics adapter é o responsável por levar as métricas do Stackdriver até a API do Kubernetes, o que permite ao HPA consumir essas métricas e agir com base nelas. Há mais detalhes na seção de troubleshooting, mais abaixo.

Para que os objetos do GKE tenham acesso às métricas armazenadas no Stackdriver, você precisa fazer o deploy do Custom Metrics Stackdriver Adapter no seu cluster.

Para rodar o Custom Metrics Adapter, você precisa dar ao seu usuário permissão para criar as roles de autorização necessárias. Basta executar o comando a seguir:

kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole cluster-admin \
--user "$(gcloud config get-value account)"

Agora vamos ao deploy do adapter propriamente dito, que é o que vai nos permitir ler as métricas do Stackdriver:

kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter.yaml

Crie um Deployment

Agora, vamos subir uma aplicação nginx simples, que mais para frente vai escalar com base no RPS medido pelo HTTP/S Load Balancer.

Crie este arquivo: 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

Agora é só fazer o deploy:

kubectl apply -f deployment.yaml

Crie o Ingress do LoadBalancer

Crie o arquivo de ingress: ingress.yaml

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

E aplique o ingress:

kubectl apply -f ingress.yaml

Crie o objeto HorizontalPodAutoscaler

É aqui que a mágica acontece.

Vamos usar uma métrica externa*, com o metricName:

loadbalancing.googleapis.com|https|

Observação: a lista completa de métricas do Stackdriver está aqui, e você também pode usar o Metrics Explorer.

Também precisamos usar um metricSelector, para garantir que estamos capturando apenas as métricas do nosso load balancer específico.

Vamos descobrir a forwarding rule do nosso 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

Agora podemos adicionar o label match na nossa configuração (repare no label "forwarding_rule_name"):

metricSelector:
matchLabels:
resource.labels.forwarding_rule_name: k8s-fw-default-basic-ingress--ffd629d77b6630de

O arquivo final fica assim: 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

Note que usamos targetAverageValue, que define quanto do valor total da métrica cada réplica consegue suportar. Isso é útil quando trabalhamos com métricas que descrevem algum trabalho ou recurso que pode ser dividido entre réplicas — no nosso caso, cada réplica dá conta de uma única (ou seja, 1) RPS. Você deve, claro, ajustar esse valor conforme a sua necessidade.

Vamos testar tudo

Vamos começar gerando tráfego para o nosso load balancer.

Como você viu no comando acima:

kubectl describe ingress basic-ingress

O IP público do nosso Ingress é: 35.190.3.165

Agora vamos meter a porrada nesse endpoint 🥊 com algumas requisições:

while true ; do curl -Ss -k --write-out '%{http_code}\n' --output /dev/null http://35.190.3.165/ ; done

Agora vamos ver se o nosso HorizontalPodAutoscaler reagiu:

kubectl describe hpa nginx-hpa

Nesse momento, podem aparecer alguns avisos, já que a métrica ainda não foi populada. Mas, depois de alguns minutos, a seção Metrics aparece preenchida:

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

E na seção "Events" dá para ver:

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

Decolamos! 🚀

Troubleshooting:

  1. Uma forma simples de checar se a métrica está sendo importada para a API de external metrics do Kubernetes é navegar pela API manualmente. Isso também ajuda a confirmar se você usou o metricSelector corretamente.

Primeiro, rodamos o proxy do Kubernetes:

kubectl proxy --port=8080

Em seguida, dá para acessar pelo localhost:

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

E este é um trecho do 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. Outro jeito de ver como o adapter está se comportando é acompanhar os logs dele. Primeiro, vamos listar os pods de custom-metrics:

$ kubectl get pods -n custom-metrics
NAME READY
custom-metrics-stackdriver-adapter-c4d98dc54-2n4jz 1/1

Por fim, vamos olhar os 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
...

Esses logs trazem muita informação útil, principalmente quando há mensagens de erro.

Conclusão

Usar métricas externas como essas, coletadas e armazenadas pelo Stackdriver, é bem direto e tranquilo. Da mesma forma, você também pode aproveitar suas próprias custom metrics publicadas no Stackdriver via Monitoring API.

Criei um repositório no GitHub com os recursos que usei neste post aqui.

Quer mais conteúdo? Confira nosso blog ou siga o Eran no Twitter.