Contrôler l'utilisation des ressources avec des notifications

Ce document explique comment utiliser les notifications budgétaires pour contrôler de manière sélective l'utilisation des ressources.

Lorsque vous désactivez la facturation sur un projet, tous les services s'arrêtent et toutes les ressources finissent par être supprimées. Si vous souhaitez obtenir une réponse plus nuancée, vous pouvez contrôler les ressources de manière sélective. Par exemple, vous pouvez arrêter certaines ressources Compute Engine tout en laissant les ressources Cloud Storage intactes. Arrêter uniquement certaines ressources réduit vos coûts sans désactiver complètement votre environnement.

Dans l'exemple suivant, le projet effectue des recherches sur plusieurs machines virtuelles (VM) Compute Engine et stocke les résultats dans des buckets Cloud Storage. En utilisant les notifications budgétaires comme déclencheur, une fois le budget dépassé, cette fonction Cloud Run arrête toutes les instances Compute Engine, mais n'affecte pas les résultats stockés.

Avant de commencer

Avant de commencer, vous devez effectuer les tâches suivantes:

  1. Activer l'API Cloud Billing
  2. Créer un budget
  3. Configurer des notifications de budget automatisées

Configurer une fonction Cloud Run

  1. Suivez la procédure décrite dans Créer une fonction Cloud Run. Assurez-vous de définir le type de déclencheur sur le même sujet Pub/Sub que celui que votre budget utilisera.
  2. Ajoutez les dépendances suivantes :

    Node.js

    Copiez ce qui suit dans votre fichier package.json:

    {
      "name": "cloud-functions-billing",
      "private": "true",
      "version": "0.0.1",
      "description": "Examples of integrating Cloud Functions with billing",
      "main": "index.js",
      "engines": {
        "node": ">=16.0.0"
      },
      "scripts": {
        "compute-test": "c8 mocha -p -j 2 test/periodic.test.js --timeout=600000",
        "test": "c8 mocha -p -j 2 test/index.test.js --timeout=5000 --exit"
      },
      "author": "Ace Nassri <anassri@google.com>",
      "license": "Apache-2.0",
      "dependencies": {
        "@google-cloud/billing": "^4.0.0",
        "@google-cloud/compute": "^4.0.0",
        "google-auth-library": "^9.0.0",
        "googleapis": "^143.0.0",
        "slack": "^11.0.1"
      },
      "devDependencies": {
        "@google-cloud/functions-framework": "^3.0.0",
        "c8": "^10.0.0",
        "gaxios": "^6.0.0",
        "mocha": "^10.0.0",
        "promise-retry": "^2.0.0",
        "proxyquire": "^2.1.0",
        "sinon": "^18.0.0",
        "wait-port": "^1.0.4"
      }
    }
    

    Python

    Copiez ce qui suit dans votre fichier requirements.txt:

    slackclient==2.9.4
    google-api-python-client==2.131.0
    

  3. Copiez le code suivant dans votre fonction Cloud Run:

    Node.js

    const {CloudBillingClient} = require('@google-cloud/billing');
    const {InstancesClient} = require('@google-cloud/compute');
    
    const PROJECT_ID = process.env.GOOGLE_CLOUD_PROJECT;
    const PROJECT_NAME = `projects/${PROJECT_ID}`;
    const instancesClient = new InstancesClient();
    const ZONE = 'us-central1-a';
    
    exports.limitUse = async pubsubEvent => {
      const pubsubData = JSON.parse(
        Buffer.from(pubsubEvent.data, 'base64').toString()
      );
      if (pubsubData.costAmount <= pubsubData.budgetAmount) {
        return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
      }
    
      const instanceNames = await _listRunningInstances(PROJECT_ID, ZONE);
      if (!instanceNames.length) {
        return 'No running instances were found.';
      }
    
      await _stopInstances(PROJECT_ID, ZONE, instanceNames);
      return `${instanceNames.length} instance(s) stopped successfully.`;
    };
    
    /**
     * @return {Promise} Array of names of running instances
     */
    const _listRunningInstances = async (projectId, zone) => {
      const [instances] = await instancesClient.list({
        project: projectId,
        zone: zone,
      });
      return instances
        .filter(item => item.status === 'RUNNING')
        .map(item => item.name);
    };
    
    /**
     * @param {Array} instanceNames Names of instance to stop
     * @return {Promise} Response from stopping instances
     */
    const _stopInstances = async (projectId, zone, instanceNames) => {
      await Promise.all(
        instanceNames.map(instanceName => {
          return instancesClient
            .stop({
              project: projectId,
              zone: zone,
              instance: instanceName,
            })
            .then(() => {
              console.log(`Instance stopped successfully: ${instanceName}`);
            });
        })
      );
    };

    Python

    import base64
    import json
    import os
    
    from googleapiclient import discovery
    
    PROJECT_ID = os.getenv("GCP_PROJECT")
    PROJECT_NAME = f"projects/{PROJECT_ID}"
    ZONE = "us-west1-b"
    
    
    def limit_use(data, context):
        pubsub_data = base64.b64decode(data["data"]).decode("utf-8")
        pubsub_json = json.loads(pubsub_data)
        cost_amount = pubsub_json["costAmount"]
        budget_amount = pubsub_json["budgetAmount"]
        if cost_amount <= budget_amount:
            print(f"No action necessary. (Current cost: {cost_amount})")
            return
    
        compute = discovery.build(
            "compute",
            "v1",
            cache_discovery=False,
        )
        instances = compute.instances()
    
        instance_names = __list_running_instances(PROJECT_ID, ZONE, instances)
        __stop_instances(PROJECT_ID, ZONE, instance_names, instances)
    
    
    def __list_running_instances(project_id, zone, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @return {Promise} Array of names of running instances
        """
        res = instances.list(project=project_id, zone=zone).execute()
    
        if "items" not in res:
            return []
    
        items = res["items"]
        running_names = [i["name"] for i in items if i["status"] == "RUNNING"]
        return running_names
    
    
    def __stop_instances(project_id, zone, instance_names, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @param {Array} instance_names Names of instance to stop
        @return {Promise} Response from stopping instances
        """
        if not len(instance_names):
            print("No running instances were found.")
            return
    
        for name in instance_names:
            instances.stop(project=project_id, zone=zone, instance=name).execute()
            print(f"Instance stopped successfully: {name}")
    
    

  4. Définissez le point d'entrée sur la fonction à exécuter:

    Node.js

    Définissez le champ Point d'entrée sur limitUse.

    Python

    Définissez le champ Point d'entrée sur limit_use.

  5. Consultez la liste des variables d'environnement définies automatiquement et déterminez si vous devez définir manuellement la variable GCP_PROJECT sur le projet exécutant les machines virtuelles.

  6. Définissez le paramètre ZONE. Ce paramètre correspond à la zone dans laquelle les instances sont arrêtées lorsque le budget est dépassé.

  7. Cliquez sur DÉPLOYER.

Configurer les autorisations du compte de service

Votre fonction Cloud Run s'exécute en tant que compte de service automatiquement créé. Pour contrôler l'utilisation, vous devez accorder au compte de service des autorisations sur tous les services du projet dont il aura besoin pour apporter des modifications en procédant comme suit:

  1. Identifiez le bon compte de service en consultant les informations de votre fonction Cloud Run. Le compte de service est répertorié au bas de la page.
  2. Accédez à la page IAM de la console Google Cloud pour définir les autorisations appropriées.

    Accéder à la page IAM

Vérifier que les instances sont arrêtées

Pour vous assurer que votre fonction fonctionne comme prévu, suivez la procédure décrite dans Tester une fonction Cloud Run.

Si l'opération aboutit, vos VM Compute Engine dans la console Google Cloud sont arrêtées.

Étape suivante

Consultez d'autres exemples de notifications programmatiques pour découvrir comment: