Programar el inicio o la detención de una VM de Compute Engine


En este tutorial se explica cómo usar Cloud Scheduler y las funciones de Cloud Run para iniciar y detener automáticamente instancias de Compute Engine de forma periódica mediante etiquetas de recursos.

Objetivos

  • Escribe y despliega un conjunto de funciones con funciones de Cloud Run que inicien y detengan instancias de Compute Engine.
  • Crea un conjunto de tareas con Cloud Scheduler que programen instancias con una etiqueta de recurso dev para que se ejecuten de 9:00 a 17:00, de lunes a viernes, para que coincidan con el horario de apertura habitual.

Costes

En este documento, se utilizan los siguientes componentes facturables de Google Cloud:

  • Cloud Scheduler
  • Cloud Run functions
  • Pub/Sub
  • Compute Engine

Para generar una estimación de costes basada en el uso previsto, utiliza la calculadora de precios.

Los usuarios nuevos Google Cloud pueden disfrutar de una prueba gratuita.

Antes de empezar

  1. Configura tu entorno para Cloud Scheduler.

    Configurar el entorno

  2. Enable the Cloud Run functions, Pub/Sub, and Compute Engine APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

Arquitectura de aplicaciones

Esta solución incluye los siguientes Google Cloud componentes:

Diagrama de arquitectura del sistema que muestra Cloud Scheduler programando una instancia de Compute Engine a través de Pub/Sub

Requisitos de ubicación

Algunos componentes solo se admiten en determinadas regiones:

  • Instancia de Compute Engine: se admite en cualquier región que figure en Regiones y zonas.
  • Funciones de Cloud Run: admitidas en las regiones que se indican en Ubicaciones.
  • Mensajes de Pub/Sub: se admiten en todo el mundo, ya que Pub/Sub es un servicio global.
  • Tareas de Cloud Scheduler con destinos de Pub/Sub: se admiten en cualquier Google Cloud ubicación.

¿Por qué no usar HTTP en lugar de Pub/Sub?

Puede simplificar esta arquitectura usando activadores HTTP de funciones de Cloud Run en lugar de activadores de Pub/Sub.

En este tutorial se usa Pub/Sub como activador de las funciones de Cloud Run porque este método era más seguro que usar HTTP. Sin embargo, HTTP también es una opción válida y ahora se puede proteger requiriendo autenticación.

Para obtener información sobre cómo proteger Cloud Run Functions, consulta la descripción general de la seguridad de Cloud Run Functions. Para ver una comparación entre los activadores HTTP y Pub/Sub, consulta la documentación sobre los activadores de funciones de Cloud Run.

Configurar la instancia de Compute Engine

Consola

  1. Ve a la página Instancias de VM de la Google Cloud consola.
    Ve a la página Instancias de VM.
  2. Haz clic en Crear instancia.
  3. Asigna el valor dev-instance a Nombre.
  4. En Etiquetas, haz clic en Añadir etiquetas.
  5. Haz clic en Añadir etiqueta.
  6. Escribe env en Clave y dev en Valor.
  7. En Región, seleccione us-west1.
  8. En Zona, selecciona us-west1-b.
  9. Haz clic en Guardar.
  10. En la parte inferior de la página, haz clic en Crear.

gcloud

gcloud compute instances create dev-instance \
    --network default \
    --zone us-west1-b \
    --labels=env=dev

Desplegar funciones activadas por Pub/Sub mediante funciones de Cloud Run

Crear y desplegar las funciones

Consola

Crea la función de inicio.

  1. Ve a la página Funciones de Cloud Run de la Google Cloud consola.
    Ve a la página de Cloud Run Functions.
  2. Haz clic en Crear función.
  3. En Entorno, selecciona 1.ª gen..
  4. Asigna el valor startInstancePubSub a Nombre de la función.
  5. Deja el valor predeterminado en Región.
  6. En Tipo de activador, selecciona Cloud Pub/Sub.
  7. En Seleccionar un tema de Cloud Pub/Sub, haz clic en Crear un tema.
  8. Debería aparecer un cuadro de diálogo Crear tema.
    1. En ID de tema, introduce start-instance-event.
    2. Haz clic en Crear para cerrar el cuadro de diálogo.
  9. Haz clic en Guardar en la parte inferior del cuadro Activador.
  10. Haz clic en Siguiente en la parte inferior de la página.
  11. En Tiempo de ejecución, selecciona Node.js 16 o una versión posterior.
  12. En Punto de entrada, escribe startInstancePubSub.
  13. En la parte izquierda del editor de código, selecciona index.js.
  14. Sustituye el código inicial por el siguiente:

    const compute = require('@google-cloud/compute');
    const instancesClient = new compute.InstancesClient();
    const operationsClient = new compute.ZoneOperationsClient();
    
    async function waitForOperation(projectId, operation) {
      while (operation.status !== 'DONE') {
        [operation] = await operationsClient.wait({
          operation: operation.name,
          project: projectId,
          zone: operation.zone.split('/').pop(),
        });
      }
    }
    
    /**
     * Starts Compute Engine instances.
     *
     * Expects a PubSub message with JSON-formatted event data containing the
     * following attributes:
     *  zone - the GCP zone the instances are located in.
     *  label - the label of instances to start.
     *
     * @param {!object} event Cloud Function PubSub message event.
     * @param {!object} callback Cloud Function PubSub callback indicating
     *  completion.
     */
    exports.startInstancePubSub = async (event, context, callback) => {
      try {
        const project = await instancesClient.getProjectId();
        const payload = _validatePayload(event);
        const options = {
          filter: `labels.${payload.label}`,
          project,
          zone: payload.zone,
        };
    
        const [instances] = await instancesClient.list(options);
    
        await Promise.all(
          instances.map(async instance => {
            const [response] = await instancesClient.start({
              project,
              zone: payload.zone,
              instance: instance.name,
            });
    
            return waitForOperation(project, response.latestResponse);
          })
        );
    
        // Operation complete. Instance successfully started.
        const message = 'Successfully started instance(s)';
        console.log(message);
        callback(null, message);
      } catch (err) {
        console.log(err);
        callback(err);
      }
    };
    
    /**
     * Validates that a request payload contains the expected fields.
     *
     * @param {!object} payload the request payload to validate.
     * @return {!object} the payload object.
     */
    const _validatePayload = event => {
      let payload;
      try {
        payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
      } catch (err) {
        throw new Error('Invalid Pub/Sub message: ' + err);
      }
      if (!payload.zone) {
        throw new Error("Attribute 'zone' missing from payload");
      } else if (!payload.label) {
        throw new Error("Attribute 'label' missing from payload");
      }
      return payload;
    };
  15. En la parte izquierda del editor de código, selecciona package.json.

  16. Sustituye el código inicial por el siguiente:

    {
      "name": "cloud-functions-schedule-instance",
      "version": "0.1.0",
      "private": true,
      "license": "Apache-2.0",
      "author": "Google Inc.",
      "repository": {
        "type": "git",
        "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
      },
      "engines": {
        "node": ">=16.0.0"
      },
      "scripts": {
        "test": "c8 mocha -p -j 2 test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "c8": "^10.0.0",
        "mocha": "^10.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^18.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^4.0.0"
      }
    }
    
  17. En la parte inferior de la página, haga clic en Implementar.

Crea la función de parada.

  1. Deberías estar en la página Funciones de Cloud Run de la Google Cloud consola.
  2. Haz clic en Crear función.
  3. En Entorno, selecciona 1.ª gen..
  4. Asigna el valor stopInstancePubSub a Nombre de la función.
  5. Deja el valor predeterminado en Región.
  6. En Tipo de activador, selecciona Cloud Pub/Sub.
  7. En Seleccionar un tema de Cloud Pub/Sub, haz clic en Crear un tema.
  8. Debería aparecer un cuadro de diálogo Crear tema.
    1. En ID de tema, introduce stop-instance-event.
    2. Haz clic en Crear para cerrar el cuadro de diálogo.
  9. Haz clic en Guardar en la parte inferior del cuadro Activador.
  10. Haz clic en Siguiente en la parte inferior de la página.
  11. En Tiempo de ejecución, selecciona Node.js 16 o una versión posterior.
  12. En Punto de entrada, escribe stopInstancePubSub.
  13. En la parte izquierda del editor de código, selecciona index.js.
  14. Sustituye el código inicial por el siguiente:

    const compute = require('@google-cloud/compute');
    const instancesClient = new compute.InstancesClient();
    const operationsClient = new compute.ZoneOperationsClient();
    
    async function waitForOperation(projectId, operation) {
      while (operation.status !== 'DONE') {
        [operation] = await operationsClient.wait({
          operation: operation.name,
          project: projectId,
          zone: operation.zone.split('/').pop(),
        });
      }
    }
    
    /**
     * Stops Compute Engine instances.
     *
     * Expects a PubSub message with JSON-formatted event data containing the
     * following attributes:
     *  zone - the GCP zone the instances are located in.
     *  label - the label of instances to stop.
     *
     * @param {!object} event Cloud Function PubSub message event.
     * @param {!object} callback Cloud Function PubSub callback indicating completion.
     */
    exports.stopInstancePubSub = async (event, context, callback) => {
      try {
        const project = await instancesClient.getProjectId();
        const payload = _validatePayload(event);
        const options = {
          filter: `labels.${payload.label}`,
          project,
          zone: payload.zone,
        };
    
        const [instances] = await instancesClient.list(options);
    
        await Promise.all(
          instances.map(async instance => {
            const [response] = await instancesClient.stop({
              project,
              zone: payload.zone,
              instance: instance.name,
            });
    
            return waitForOperation(project, response.latestResponse);
          })
        );
    
        // Operation complete. Instance successfully stopped.
        const message = 'Successfully stopped instance(s)';
        console.log(message);
        callback(null, message);
      } catch (err) {
        console.log(err);
        callback(err);
      }
    };
    
    /**
     * Validates that a request payload contains the expected fields.
     *
     * @param {!object} payload the request payload to validate.
     * @return {!object} the payload object.
     */
    const _validatePayload = event => {
      let payload;
      try {
        payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
      } catch (err) {
        throw new Error('Invalid Pub/Sub message: ' + err);
      }
      if (!payload.zone) {
        throw new Error("Attribute 'zone' missing from payload");
      } else if (!payload.label) {
        throw new Error("Attribute 'label' missing from payload");
      }
      return payload;
    };
  15. En la parte izquierda del editor de código, selecciona package.json.

  16. Sustituye el código inicial por el siguiente:

    {
      "name": "cloud-functions-schedule-instance",
      "version": "0.1.0",
      "private": true,
      "license": "Apache-2.0",
      "author": "Google Inc.",
      "repository": {
        "type": "git",
        "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
      },
      "engines": {
        "node": ">=16.0.0"
      },
      "scripts": {
        "test": "c8 mocha -p -j 2 test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "c8": "^10.0.0",
        "mocha": "^10.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^18.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^4.0.0"
      }
    }
    
  17. En la parte inferior de la página, haga clic en Implementar.

gcloud

Crea los temas de Pub/Sub.

gcloud pubsub topics create start-instance-event
gcloud pubsub topics create stop-instance-event

Obtén el código

  1. Descarga el código.

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    También puedes descargar el ejemplo como un archivo ZIP y extraerlo.

  2. Ve al directorio correcto.

    cd nodejs-docs-samples/functions/scheduleinstance/

Crea las funciones de inicio y de finalización.

Deberías estar en el directorio nodejs-docs-samples/functions/scheduleinstance/.

gcloud functions deploy startInstancePubSub \
    --trigger-topic start-instance-event \
    --runtime nodejs18 \
    --allow-unauthenticated
gcloud functions deploy stopInstancePubSub \
    --trigger-topic stop-instance-event \
    --runtime nodejs18 \
    --allow-unauthenticated

(Opcional) Verificar que las funciones funcionan

Consola

Detener la instancia

  1. Ve a la página Funciones de Cloud Run de la Google Cloud consola.
    Ve a la página de Cloud Run Functions.
  2. Haz clic en la función llamada stopInstancePubSub.
  3. Verás varias pestañas: General, Activador, Fuente, Permisos y Pruebas. Haz clic en la pestaña Pruebas.
  4. En Evento activador, introduzca lo siguiente:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • Se trata simplemente de la cadena codificada en base64 de {"zone":"us-west1-b", "label":"env=dev"}

    • Si quieres codificar tu propia cadena, puedes usar cualquier herramienta de codificación base64 online.

  5. Haz clic en el botón Probar la función.

  6. Cuando termine de ejecutarse, deberías ver Successfully stopped instance dev-instance impreso en Output (Salida). Puede tardar hasta 60 segundos en completarse.

    • Si ves error: 'Error: function failed to load.', espera unos 10 segundos a que se termine de implementar la función y vuelve a intentarlo.

    • Si ves error: 'Error: function execution attempt timed out.', ve al siguiente paso para comprobar si la instancia tarda mucho en cerrarse.

    • Si, por el contrario, termina de ejecutarse, pero no muestra nada, probablemente también se haya agotado el tiempo de espera. Ve al siguiente paso para comprobar si la instancia tarda mucho en cerrarse.

  7. Ve a la página Instancias de VM de la Google Cloud consola.
    Ve a la página Instancias de VM.

  8. Verifica que la instancia llamada dev-instance tenga un cuadrado gris junto a su nombre, lo que indica que se ha detenido. Puede tardar hasta 30 segundos en apagarse.

    • Si parece que no termina, prueba a hacer clic en Actualizar en la parte superior de la página.

Iniciar la instancia

  1. Ve a la página Funciones de Cloud Run de la Google Cloud consola.
    Ve a la página de Cloud Run Functions.
  2. Haz clic en la función llamada startInstancePubSub.
  3. Verás varias pestañas: General, Activador, Fuente, Permisos y Pruebas. Haz clic en la pestaña Pruebas.
  4. En Evento activador, introduzca lo siguiente:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • De nuevo, se trata simplemente de la cadena codificada en Base64 de {"zone":"us-west1-b", "label":"env=dev"}
  5. Haz clic en el botón Probar la función.

  6. Cuando termine de ejecutarse, deberías ver Successfully started instance dev-instance impreso en Output (Salida).

  7. Ve a la página Instancias de VM de la Google Cloud consola.
    Ve a la página Instancias de VM.

  8. Verifica que la instancia llamada dev-instance tenga una marca de verificación verde junto a su nombre, lo que indica que está en ejecución. Puede tardar hasta 30 segundos en iniciarse.

gcloud

Detener la instancia

  1. Llama a la función para detener la instancia.

    gcloud functions call stopInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • Se trata simplemente de la cadena codificada en base64 de {"zone":"us-west1-b", "label":"env=dev"}

    • Si quieres codificar tu propia cadena, puedes usar cualquier herramienta. A continuación, se muestra un ejemplo en el que se usa la herramienta de línea de comandos base64:

      echo '{"zone":"us-west1-b", "label":"env=dev"}' | base64
      
      eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo=
      

    Cuando la función haya terminado, debería ver lo siguiente:

    result: Successfully stopped instance dev-instance
    

    Puede tardar hasta 60 segundos en completarse.

    • Si, por el contrario, aparece el siguiente error:

      error: 'Error: function failed to load.`
      

      Espera unos 10 segundos a que se termine de implementar la función y vuelve a intentarlo.

    • Si, por el contrario, aparece el siguiente error:

      error: `Error: function execution attempt timed out.`
      

      Ve al siguiente paso para comprobar si la instancia tarda mucho en apagarse.

    • Si no obtienes ningún resultado, es probable que la función haya agotado el tiempo de espera. Ve al siguiente paso para comprobar si la instancia tarda mucho en apagarse.

  2. Comprueba que la instancia tenga el estado TERMINATED. Puede tardar hasta 30 segundos en apagarse.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: TERMINATED
    

Iniciar la instancia

  1. Llama a la función para iniciar la instancia.

    gcloud functions call startInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • De nuevo, se trata simplemente de la cadena codificada en Base64 de {"zone":"us-west1-b", "label":"env=dev"}

    Cuando la función haya terminado, debería ver lo siguiente:

    result: Successfully started instance dev-instance
    
  2. Comprueba que la instancia tenga el estado RUNNING. Puede tardar hasta 30 segundos en iniciarse.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: RUNNING
    

Configurar las tareas de Cloud Scheduler para llamar a Pub/Sub

Crear las tareas

Consola

Crea la tarea de inicio.

  1. Ve a la página Cloud Scheduler de la consola de Google Cloud .
    Ve a la página Cloud Scheduler.
  2. Haz clic en Crear un trabajo.
  3. Deja la región predeterminada.
  4. Asigna el valor startup-dev-instances a Nombre.
  5. En Frecuencia, escribe 0 9 * * 1-5.
    • Se ejecutará todos los días de lunes a viernes a las 9:00.
  6. En Zona horaria, selecciona el país y la zona horaria que quieras. En este ejemplo se usarán United States y Los Angeles.
  7. Haz clic en Continuar.
  8. En Target type (Tipo de objetivo), selecciona Pub/Sub.
  9. Selecciona start-instance-event en el menú desplegable de temas.
  10. En Mensaje, introduce lo siguiente:
    {"zone":"us-west1-b","label":"env=dev"}
    
  11. Haz clic en Crear.

Crea la tarea de detención.

  1. Deberías estar en la página Cloud Scheduler de la consola de Google Cloud .
  2. Haz clic en Crear trabajo.
  3. Deje la región predeterminada y haga clic en Siguiente en la parte inferior de la página.
  4. Asigna el valor shutdown-dev-instances a Nombre.
  5. En Frecuencia, escribe 0 17 * * 1-5.
    • Se ejecutará a las 17:00 todos los días de lunes a viernes.
  6. En Zona horaria, selecciona el país y la zona horaria que quieras. En este ejemplo se usarán United States y Los Angeles.
  7. Haz clic en Continuar.
  8. En Target type (Tipo de objetivo), selecciona Pub/Sub.
  9. Selecciona stop-instance-event en el menú desplegable de temas.
  10. En Mensaje, introduce lo siguiente:
    {"zone":"us-west1-b","label":"env=dev"}
    
  11. Haz clic en Crear.

gcloud

Crea la tarea de inicio.

gcloud scheduler jobs create pubsub startup-dev-instances \
    --schedule '0 9 * * 1-5' \
    --topic start-instance-event \
    --message-body '{"zone":"us-west1-b", "label":"env=dev"}' \
    --time-zone 'America/Los_Angeles' \
    --location us-central1

Crea la tarea de detención.

gcloud scheduler jobs create pubsub shutdown-dev-instances \
    --schedule '0 17 * * 1-5' \
    --topic stop-instance-event \
    --message-body '{"zone":"us-west1-b", "label":"env=dev"}' \
    --time-zone 'America/Los_Angeles' \
    --location us-central1

(Opcional) Verificar que las tareas funcionan

Consola

Detener la instancia

  1. Ve a la página Cloud Scheduler de la consola de Google Cloud .
    Ve a la página Cloud Scheduler.
  2. En el trabajo llamado shutdown-dev-instances, haz clic en el botón Ejecutar ahora, situado en el extremo derecho de la página.
  3. Ve a la página Instancias de VM de la Google Cloud consola.
    Ve a la página Instancias de VM.
  4. Verifica que la instancia llamada dev-instance tenga un cuadrado gris junto a su nombre, lo que indica que se ha detenido. Puede tardar hasta 30 segundos en apagarse.

Iniciar la instancia

  1. Ve a la página Cloud Scheduler de la consola de Google Cloud .
    Ve a la página Cloud Scheduler.
  2. En el trabajo llamado startup-dev-instances, haz clic en el botón Ejecutar ahora, situado en el extremo derecho de la página.
  3. Ve a la página Instancias de VM de la Google Cloud consola.
    Ve a la página Instancias de VM.
  4. Verifica que la instancia llamada dev-instance tenga una marca de verificación verde junto a su nombre, lo que indica que está en ejecución. Puede tardar hasta 30 segundos en iniciarse.

gcloud

Detener la instancia

  1. Ejecuta la tarea del programador para detener la instancia.

    gcloud beta scheduler jobs run shutdown-dev-instances
    
  2. Comprueba que la instancia tenga el estado TERMINATED. Puede tardar hasta 30 segundos en apagarse.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: TERMINATED
    

Iniciar la instancia

  1. Ejecuta la tarea del programador para iniciar la instancia.

    gcloud beta scheduler jobs run startup-dev-instances
    
  2. Comprueba que la instancia tenga el estado RUNNING. Puede tardar hasta 30 segundos en iniciarse.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: RUNNING
    

Limpieza

Cuando hayas terminado el tutorial, puedes eliminar los recursos que has creado para que dejen de usar cuota y generar cargos. En las siguientes secciones se explica cómo eliminar o desactivar dichos recursos.

Elimina las tareas de Cloud Scheduler

  1. Ve a la página Cloud Scheduler de la consola de Google Cloud .

    Ve a la página Cloud Scheduler.

  2. Haz clic en las casillas situadas junto a los trabajos.

  3. Haz clic en el botón Eliminar situado en la parte superior de la página y confirma la eliminación.

Eliminar los temas de Pub/Sub

  1. Ve a la página Pub/Sub de la Google Cloud consola.

    Ir a la página de Pub/Sub

  2. Marca las casillas situadas junto a los temas.

  3. Haz clic en Eliminar en la parte superior de la página y confirma que quieres eliminarlo.

Eliminar las funciones desplegadas a través de Cloud Run Functions

  1. Ve a la página Funciones de Cloud Run de la Google Cloud consola.

    Ve a la página Cloud Run functions.

  2. Marca las casillas situadas junto a las funciones.

  3. Haz clic en el botón Eliminar situado en la parte superior de la página y confirma la eliminación.

Eliminar la instancia de Compute Engine

Para eliminar una instancia de Compute Engine:

  1. In the Google Cloud console, go to the VM instances page.

    Go to VM instances

  2. Select the checkbox for the instance that you want to delete.
  3. To delete the instance, click More actions, click Delete, and then follow the instructions.

Eliminar el proyecto

La forma más fácil de evitar que te cobren es eliminar el proyecto que has creado para el tutorial.

Para ello, sigue las instrucciones que aparecen a continuación:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Siguientes pasos