Ajustar automáticamente la escala de los Deployment con métricas de Cloud Monitoring

En este instructivo, se muestra cómo escalar automáticamente tus cargas de trabajo de Google Kubernetes Engine (GKE) en función de las métricas disponibles en Cloud Monitoring.

En este instructivo, puedes configurar el ajuste de escala automático en función de una de las cuatro métricas diferentes:

CPU

Uso de CPU

Escala según el porcentaje de uso de CPU entre los nodos. Esto puede ser rentable, lo que te permite maximizar el uso de los recursos de CPU. Sin embargo, debido a que el uso de CPU es una métrica final, tus usuarios pueden experimentar latencia mientras se realiza el escalamiento vertical.

Pub/Sub

Tareas pendientes de Pub/Sub

Escala según la cantidad de mensajes no confirmados restantes en una suscripción de Pub/Sub. Esta opción puede reducir la latencia de manera eficiente antes de que se convierta en un problema, pero puede usar relativamente más recursos que el ajuste de escala automático basado en el uso de CPU.

Métrica personalizada

Métrica personalizada de Cloud Monitoring

Escala en función de una métrica personalizada definida por el usuario que exportan las bibliotecas cliente de Cloud Monitoring. Para obtener más información, consulta Crea métricas personalizadas en la documentación de Cloud Monitoring.

Prometheus personalizado

Métrica personalizada de Prometheus

Escala en función de una métrica personalizada definida por el usuario exportada en el formato Prometheus. La métrica de Prometheus debe ser del tipo Gauge y no debe contener el prefijo custom.googleapis.com.

El ajuste de escala automático consiste, en esencia, en encontrar un equilibrio aceptable entre el costo y la latencia. Recomendamos que pruebes con una combinación de estas métricas y otras para encontrar una política que se adapte a tus necesidades.

Objetivos

En este instructivo, se abarcan las siguientes tareas:

  1. Cómo implementar el adaptador de métricas personalizadas
  2. Cómo exportar métricas desde el código de la aplicación
  3. Cómo ver tus métricas en la interfaz de Cloud Monitoring
  4. Cómo implementar un recurso HorizontalPodAutoscaler (HPA) para escalar la aplicación según las métricas de Cloud Monitoring

Costos

En este instructivo, se usan los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices este instructivo, podrás borrar los recursos creados para evitar que se te siga facturando. Para obtener más información, consulta Cómo realizar una limpieza.

Antes de comenzar

Sigue los pasos que se indican a continuación para habilitar la API de Kubernetes Engine:
  1. Consulta la página Kubernetes Engine en Google Cloud Console.
  2. Crea o selecciona un proyecto.
  3. Espera a que la API y los servicios relacionados se habiliten. Esto puede tomar varios minutos.
  4. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información sobre cómo verificar si la facturación está habilitada en un proyecto.

Puedes seguir este instructivo mediante Cloud Shell, que viene preinstalado con las herramientas de línea de comandos de gcloud y kubectl, que se usan en este instructivo. Si usas Cloud Shell, no necesitas instalar estas herramientas de línea de comandos en tu estación de trabajo.

Para usar Cloud Shell, sigue estos pasos:

  1. Ve a Google Cloud Console.
  2. Haz clic en el botón Activar Cloud Shell Botón de activar Shell que se encuentra en la parte superior de la ventana de la consola.

    Se abrirá una sesión de Cloud Shell en un marco nuevo en la parte inferior de la consola, que mostrará una ventana emergente con una línea de comandos.

    Sesión de Cloud Shell

Configure su entorno

  1. Establece la zona predeterminada para Google Cloud CLI:

    gcloud config set compute/zone zone
    

    Reemplaza lo siguiente:

    • zone: Elige la zona más cercana a ti. Para obtener más información, consulta Regiones y zonas.
  2. Establece la variable de entorno PROJECT_ID en el ID del proyecto de Google Cloud (project-id):

    export PROJECT_ID=project-id
    
  3. Establece la zona predeterminada para Google Cloud CLI:

    gcloud config set project $PROJECT_ID
    
  4. Crea un clúster de GKE:

    gcloud container clusters create metrics-autoscaling
    

Implementa el adaptador de métricas personalizadas

El adaptador de métricas personalizadas permite que tu clúster envíe y reciba métricas mediante Cloud Monitoring.

CPU

No aplicable: los escaladores automáticos de pods horizontales pueden escalar de forma nativa en función del uso de CPU, por lo que no se necesita el adaptador de métricas personalizadas.

Pub/Sub

Otórgale al usuario la capacidad de crear las funciones de autorización requeridas:

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

Implementa el adaptador del modelo de recursos nuevo en el clúster:

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

Métrica personalizada

Otórgale al usuario la capacidad de crear las funciones de autorización requeridas:

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

Implementa el adaptador del modelo de recursos en el clúster:

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

Prometheus personalizado

Otórgale al usuario la capacidad de crear las funciones de autorización requeridas:

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

Implementa el adaptador de modelo de recursos heredado en el clúster:

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

Implementa una aplicación con métricas

Descarga el repositorio que contiene el código de la aplicación para este instructivo:

CPU

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git
cd kubernetes-engine-samples/hello-app

Pub/Sub

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git
cd kubernetes-engine-samples/cloud-pubsub

Métrica personalizada

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git
cd kubernetes-engine-samples/custom-metrics-autoscaling/direct-to-sd

Prometheus personalizado

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git
cd kubernetes-engine-samples/custom-metrics-autoscaling/prometheus-to-sd

El repositorio contiene código que exporta métricas a Cloud Monitoring:

CPU

Esta aplicación responde “Hello, world!” a cualquier solicitud web en el puerto 8080. Cloud Monitoring recopila de forma automática las métricas de CPU de Compute Engine.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	// register hello function to handle all requests
	mux := http.NewServeMux()
	mux.HandleFunc("/", hello)

	// use PORT environment variable, or default to 8080
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	// start the web server on port and accept requests
	log.Printf("Server listening on port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, mux))
}

// hello responds to the request with a plain-text "Hello, world" message.
func hello(w http.ResponseWriter, r *http.Request) {
	log.Printf("Serving request: %s", r.URL.Path)
	host, _ := os.Hostname()
	fmt.Fprintf(w, "Hello, world!\n")
	fmt.Fprintf(w, "Version: 1.0.0\n")
	fmt.Fprintf(w, "Hostname: %s\n", host)
}

Pub/Sub

Esta aplicación sondea una suscripción de Pub/Sub para los mensajes nuevos y los confirma a medida que llegan. Cloud Monitoring recopila de forma automática las métricas de suscripción de Pub/Sub.

def main():
    """Continuously pull messages from subsciption"""
    client = pubsub.Client()
    subscription = client.topic(PUBSUB_TOPIC).subscription(PUBSUB_SUBSCRIPTION)

    print('Pulling messages from Pub/Sub subscription...')
    while True:
        with pubsub.subscription.AutoAck(subscription, max_messages=10) as ack:
            for _, message in list(ack.items()):
                print("[{0}] Received message: ID={1} Data={2}".format(
                    datetime.datetime.now(),
                    message.message_id,
                    message.data))
                process(message)

def process(message):
    """Process received message"""
    print("[{0}] Processing: {1}".format(datetime.datetime.now(),
                                         message.message_id))
    time.sleep(3)
    print("[{0}] Processed: {1}".format(datetime.datetime.now(),
                                        message.message_id))

Métrica personalizada

Esta aplicación exporta una métrica de valor constante mediante las bibliotecas cliente de Cloud Monitoring.

func exportMetric(stackdriverService *monitoring.Service, metricName string,
	metricValue int64, metricLabels map[string]string, monitoredResource string, resourceLabels map[string]string) error {
	dataPoint := &monitoring.Point{
		Interval: &monitoring.TimeInterval{
			EndTime: time.Now().Format(time.RFC3339),
		},
		Value: &monitoring.TypedValue{
			Int64Value: &metricValue,
		},
	}
	// Write time series data.
	request := &monitoring.CreateTimeSeriesRequest{
		TimeSeries: []*monitoring.TimeSeries{
			{
				Metric: &monitoring.Metric{
					Type:   "custom.googleapis.com/" + metricName,
					Labels: metricLabels,
				},
				Resource: &monitoring.MonitoredResource{
					Type:   monitoredResource,
					Labels: resourceLabels,
				},
				Points: []*monitoring.Point{
					dataPoint,
				},
			},
		},
	}
	projectName := fmt.Sprintf("projects/%s", resourceLabels["project_id"])
	_, err := stackdriverService.Projects.TimeSeries.Create(projectName, request).Do()
	return err
}

Prometheus personalizado

Esta aplicación exporta una métrica de valor constante con el formato Prometheus.

metric := prometheus.NewGauge(
	prometheus.GaugeOpts{
		Name: *metricName,
		Help: "Custom metric",
	},
)
prometheus.MustRegister(metric)
metric.Set(float64(*metricValue))

http.Handle("/metrics", promhttp.Handler())
log.Printf("Starting to listen on :%d", *port)
err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)

El repositorio también contiene un manifiesto de Kubernetes para implementar la aplicación en tu clúster:

CPU

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloweb
  labels:
    app: hello
spec:
  selector:
    matchLabels:
      app: hello
      tier: web
  template:
    metadata:
      labels:
        app: hello
        tier: web
    spec:
      containers:
      - name: hello-app
        image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 200m

Pub/Sub

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pubsub
spec:
  selector:
    matchLabels:
      app: pubsub
  template:
    metadata:
      labels:
        app: pubsub
    spec:
      volumes:
      - name: google-cloud-key
        secret:
          secretName: pubsub-key
      containers:
      - name: subscriber
        image: us-docker.pkg.dev/google-samples/containers/gke/pubsub-sample:v1
        volumeMounts:
        - name: google-cloud-key
          mountPath: /var/secrets/google
        env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /var/secrets/google/key.json

Métrica personalizada

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: custom-metric-sd
  name: custom-metric-sd
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: custom-metric-sd
  template:
    metadata:
      labels:
        run: custom-metric-sd
    spec:
      containers:
      - command: ["./sd-dummy-exporter"]
        args:
        - --use-new-resource-model=true
        - --use-old-resource-model=false
        - --metric-name=custom-metric
        - --metric-value=40
        - --pod-name=$(POD_NAME)
        - --namespace=$(NAMESPACE)
        image: us-docker.pkg.dev/google-samples/containers/gke/sd-dummy-exporter:v0.3.0
        name: sd-dummy-exporter
        resources:
          requests:
            cpu: 100m
        env:
        # save Kubernetes metadata as environment variables for use in metrics
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace

Prometheus personalizado

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: custom-metric-prometheus-sd
  name: custom-metric-prometheus-sd
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: custom-metric-prometheus-sd
  template:
    metadata:
      labels:
        run: custom-metric-prometheus-sd
    spec:
      containers:
      # sample container generating custom metrics
      - name: prometheus-dummy-exporter
        image: us-docker.pkg.dev/google-samples/containers/gke/prometheus-dummy-exporter:v0.2.0
        command: ["./prometheus-dummy-exporter"]
        args:
        - --metric-name=custom_prometheus
        - --metric-value=40
        - --port=8080
      # pre-built 'prometheus-to-sd' sidecar container to export prometheus
      # metrics to Stackdriver
      - name: prometheus-to-sd
        image: gcr.io/google-containers/prometheus-to-sd:v0.5.0
        command: ["/monitor"]
        args:
        - --source=:http://localhost:8080
        - --stackdriver-prefix=custom.googleapis.com
        - --pod-id=$(POD_ID)
        - --namespace-id=$(POD_NAMESPACE)
        env:
        # save Kubernetes metadata as environment variables for use in metrics
        - name: POD_ID
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.uid
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace

Implementa la aplicación en el clúster:

CPU

kubectl apply -f manifests/helloweb-deployment.yaml

Pub/Sub

Habilita la API de Pub/Sub en tu proyecto:

gcloud services enable cloudresourcemanager.googleapis.com pubsub.googleapis.com

Crea un tema y una suscripción de Pub/Sub:

gcloud pubsub topics create echo
gcloud pubsub subscriptions create echo-read --topic=echo

Crea una cuenta de servicio con acceso a Pub/Sub:

gcloud iam service-accounts create autoscaling-pubsub-sa
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:autoscaling-pubsub-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --role "roles/pubsub.subscriber"

Descarga el archivo de claves de la cuenta de servicio:

gcloud iam service-accounts keys create key.json \
  --iam-account autoscaling-pubsub-sa@$PROJECT_ID.iam.gserviceaccount.com

Importa la clave de la cuenta de servicio al clúster como un Secreto:

kubectl create secret generic pubsub-key --from-file=key.json=./key.json

Implementa la aplicación en el clúster:

kubectl apply -f deployment/pubsub-with-secret.yaml

Métrica personalizada

kubectl apply -f custom-metrics-sd.yaml

Prometheus personalizado

kubectl apply -f custom-metrics-prometheus-sd.yaml

Después de esperar un momento a que se implemente la aplicación, todos los pods alcanzan el estado Ready:

CPU

kubectl get pods

Resultado:

NAME                        READY   STATUS    RESTARTS   AGE
helloweb-7f7f7474fc-hzcdq   1/1     Running   0          10s

Pub/Sub

kubectl get pods

Resultado:

NAME                     READY   STATUS    RESTARTS   AGE
pubsub-8cd995d7c-bdhqz   1/1     Running   0          58s

Métrica personalizada

kubectl get pods

Resultado:

NAME                                READY   STATUS    RESTARTS   AGE
custom-metric-sd-58dbf4ffc5-tm62v   1/1     Running   0          33s

Prometheus personalizado

kubectl get pods

Salida:

NAME                                           READY   STATUS    RESTARTS   AGE
custom-metric-prometheus-sd-697bf7c7d7-ns76p   2/2     Running   0          49s

Visualiza métricas en Cloud Monitoring

Mientras se ejecuta tu aplicación, escribe las métricas en Cloud Monitoring.

Para consultar las métricas de un recurso supervisado mediante el Explorador de métricas, haz lo siguiente:

  1. En Google Cloud Console, ve a Monitoring o usa el siguiente botón:
    Ir a Monitoring
  2. En el panel de navegación de Monitoring, haz clic en  Explorador de métricas.
  3. En la barra de herramientas, selecciona la pestaña Explorador.
  4. Selecciona la pestaña Configuración.
  5. Expande el menú Selecciona una métrica y, luego, usa los submenús para seleccionar un tipo de recurso y una métrica. Por ejemplo, para graficar la utilización de CPU de una máquina virtual, haz lo siguiente:
    1. (Opcional) Para reducir las opciones del menú, ingresa parte del nombre de la métrica en la Barra de filtros. En este ejemplo, ingresa utilization.
    2. En el menú Recursos activos, selecciona Instancia de VM.
    3. En el menú Categorías de métricas activas, selecciona Instancia.
    4. En el menú Métricas activas, selecciona Uso de CPU.

El tipo de recurso y las métricas son los siguientes:

CPU

Explorador de métricas

Tipo de recurso: gce_instance

Métrica: compute.googleapis.com/instance/cpu/utilization

Pub/Sub

Explorador de métricas

Tipo de recurso: pubsub_subscription

Métrica: pubsub.googleapis.com/subscription/num_undelivered_messages

Métrica personalizada

Explorador de métricas

Tipo de recurso: k8s_pod

Métrica: custom.googleapis.com/custom-metric

Prometheus personalizado

Explorador de métricas

Tipo de recurso: gke_container

Métrica: custom.googleapis.com/custom_prometheus

Crea un objeto HorizontalPodAutoscaler

Cuando veas tu métrica en Cloud Monitoring, puedes implementar un HorizontalPodAutoscaler para cambiar el tamaño del Deployment en función de la métrica.

CPU

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: cpu
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: helloweb
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 30

Pub/Sub

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: pubsub
spec:
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - external:
      metric:
       name: pubsub.googleapis.com|subscription|num_undelivered_messages
       selector:
         matchLabels:
           resource.labels.subscription_id: echo-read
      target:
        type: AverageValue
        averageValue: 2
    type: External
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: pubsub

Métrica personalizada

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: custom-metric-sd
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: custom-metric-sd
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Pods
    pods:
      metric:
        name: custom-metric
      target:
        type: AverageValue
        averageValue: 20

Prometheus personalizado

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: custom-prometheus-hpa
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: custom-metric-prometheus-sd
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Pods
    pods:
      metric:
        name: custom_prometheus
      target:
        type: AverageValue
        averageValue: 20

Implementa el HorizontalPodAutoscaler en el clúster:

CPU

kubectl apply -f manifests/helloweb-hpa.yaml

Pub/Sub

kubectl apply -f deployment/pubsub-hpa.yaml

Métrica personalizada

kubectl apply -f custom-metrics-sd-hpa.yaml

Prometheus personalizado

kubectl apply -f custom-metrics-prometheus-sd-hpa.yaml

Genera la carga

En el caso de algunas métricas, es posible que debas generar la carga para observar el ajuste de escala automático:

CPU

Simula 10,000 solicitudes al servidor helloweb:

 kubectl exec -it deployments/helloweb -- /bin/sh -c \
     "for i in $(seq -s' ' 1 10000); do wget -q -O- localhost:8080; done"

Pub/Sub

Publica 200 mensajes en el tema de Pub/Sub:

for i in {1..200}; do gcloud pubsub topics publish echo --message="Autoscaling #${i}"; done

Métrica personalizada

No aplicable: El código que se usa en este ejemplo exporta un valor constante de 40 para la métrica personalizada. El HorizontalPodAutoscaler se establece con un valor objetivo de 20, por lo que intenta escalar verticalmente el Deployment de forma automática.

Prometheus personalizado

No aplicable: El código que se usa en este ejemplo exporta un valor constante de 40 para la métrica personalizada. El HorizontalPodAutoscaler se establece con un valor objetivo de 20, por lo que intenta escalar verticalmente el Deployment de forma automática.

Observa el escalamiento vertical de HorizontalPodAutoscaler

Para verificar la cantidad actual de réplicas de tu Deployment, ejecuta el siguiente comando:

kubectl get deployments

Después de darle tiempo a la métrica para que se propague, el Deployment crea cinco pods para controlar las tareas pendientes.

También puedes inspeccionar el estado y la actividad reciente de HorizontalPodAutoscaler si ejecutas lo siguiente:

kubectl describe hpa

Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

CPU

Borra tu clúster de GKE:

 gcloud container clusters delete metrics-autoscaling

Pub/Sub

  1. Limpia la suscripción y el tema de Pub/Sub:

    gcloud pubsub subscriptions delete echo-read
    gcloud pubsub topics delete echo
    
  2. Borra tu clúster de GKE:

    gcloud container clusters delete metrics-autoscaling
    

Métrica personalizada

Borra tu clúster de GKE:

 gcloud container clusters delete metrics-autoscaling

Prometheus personalizado

Borra tu clúster de GKE:

 gcloud container clusters delete metrics-autoscaling

¿Qué sigue?