Contrôler l'utilisation des ressources avec des notifications

Ce document explique comment utiliser les notifications budgétaires pour contrôler sélectivement l'utilisation des ressources.

Lorsque vous désactivez la facturation pour 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. L'arrêt de certaines ressources uniquement 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 de budget comme déclencheur, cette fonction Cloud Run arrête toutes les instances Compute Engine une fois le budget dépassé, 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 créé automatiquement. Pour contrôler l'utilisation, vous devez accorder au compte de service des autorisations sur tous les services du projet qu'il doit modifier. Pour ce faire, procédez comme suit :

  1. Pour identifier le bon compte de service, consultez les informations de la 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

Tester l'arrêt des instances

Pour vous assurer que votre fonction fonctionne comme prévu, suivez les étapes décrites dans Tester une fonction Cloud Run.

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

Étapes suivantes

Consultez d'autres exemples de notifications programmatiques pour découvrir comment effectuer les opérations suivantes :