Ejemplos de notificaciones de presupuesto programáticos

Si te preocupan los costos y necesitas controlar el entorno relacionado con tu presupuesto, puedes usar las notificaciones de presupuesto programáticas para automatizar las acciones de presupuesto.

Las notificaciones de presupuesto usan notificaciones de Cloud Pub/Sub para proporcionar el estado de presupuesto en tiempo real, mediante la escalabilidad, flexibilidad y confiabilidad del middleware empresarial orientado a los mensajes para la nube.

En esta página, se muestran instrucciones paso a paso y ejemplos sobre cómo usar las notificaciones de presupuesto con Cloud Functions a fin de automatizar la administración de los costos.

Configura las notificaciones de presupuesto

El primer paso es habilitar una notificación de Cloud Pub/Sub para tu presupuesto. Este paso se describe en Administrar las notificaciones.

Después de habilitar las notificaciones de presupuesto, ten en cuenta lo siguiente:

  • Pub/Sub Topic (Tema Pub/Sub): es el extremo de las notificaciones configurado para el presupuesto.
  • Budget ID (ID de presupuesto): es un ID único para tu presupuesto que se incluye en todas las notificaciones. Puedes ubicar el ID de presupuesto en el presupuesto en Manage notifications (Administrar las notificaciones). El ID se muestra después de que seleccionas Connect a Pub/Sub topic to this budget (Conecta un tema de Pub/Sub a este presupuesto).

Administra las notificaciones de facturación en GCP Console

Escucha tus notificaciones

El próximo paso es escuchar tus notificaciones mediante la suscripción a tu tema de Pub/Sub. Si no tienes un suscriptor, Pub/Sub descartará los mensajes publicados y no puedes recuperarlos más tarde.

Aunque existen muchas maneras en las que puedes subscribir tu tema, para estos ejemplos usaremos activadores de Cloud Function.

Crea una Cloud Function

Para crear una Cloud Function nueva, haz lo siguiente:

  1. Dirígete a la página Cloud Functions de Google Cloud Platform Console. Crea una función nueva y otórgale un nombre que sea significativo para tu presupuesto.
  2. En Trigger (Activador), selecciona Cloud Pub/Sub topic.
  3. Selecciona el tema que configuraste en tu presupuesto.
  4. Proporciona un código fuente y dependencias para que ejecute la función.

Crea una función nueva en GCP Console

Describe tu Cloud Function

Para decirle a tu Cloud Function lo que quieres que haga con la notificación, puedes escribir el código con el editor intercalado o subir un archivo. Para obtener más detalles sobre las notificaciones que recibirá tu código, consulta Notification Format (Formato de notificación).

Por ejemplo, una función puede registrar notificaciones, atributos y datos de Pub/Sub cuando se activa por una notificación de presupuesto. Para obtener más información, consulta Google Cloud Pub/Sub Triggers (Activadores de Google Cloud Pub/Sub).

Visualiza tus eventos de Cloud Function

Después de guardar la Cloud Function, puedes hacer clic en VIEW LOGS (Visualiza los registros) para ver tus notificaciones de presupuesto registradas. Esto muestra los registros de tus invocaciones de función.

Visualiza los eventos de Cloud Function en GCP Console

Envía notificaciones a Slack

Los correos electrónicos no son siempre la mejor manera de mantenerte actualizado con los costos de la nube, en particular si tu presupuesto es fundamental y urgente. Con las notificaciones, puedes reenviar los mensajes de presupuesto a otros medios.

En este ejemplo, se describe cómo reenviar notificaciones de presupuesto a Slack. De esa manera, cada vez que Cloud Billing publique una notificación de presupuesto, una Cloud Function usa un bot para publicar un mensaje en un canal de Slack del espacio de trabajo del bot.

Configura un canal y permisos de Slack

El primer paso es crear tu espacio de trabajo de Slack y los tokens de usuario bot que se usan para llamar a la API de Slack. Los tokens de la API se pueden administrar en https://api.slack.com/apps. Para obtener instrucciones más detalladas, consulta Bot Users (Usuarios de bot) en el sitio de Slack.

Configura las notificaciones de Slack

Escribe una Cloud Function

  1. Crea una función nueva mediante los pasos que se describen en Crea una Cloud Function.

  2. Agrega dependencias:

    Node.js 8 (Beta)

    Agrega una dependencia al paquete de administración de socios de red de slack para el archivo package.json de tu función.
    {
      "name": "cloud-functions-billing",
      "version": "0.0.1",
      "dependencies": {
        "slack": "^11.0.1"
      }
    }
    

    Python (Beta)

    Agrega slackclient==1.3.0 al archivo requirements.txt de tu función:
    slackclient==1.3.0

  3. Escribe el código para publicar notificaciones de presupuesto en un canal de chat de Slack mediante la API de Slack.

  4. Configura los siguientes parámetros postMessage de la API de Slack en tu código:

    • Token de acceso OAuth de usuario bot
    • Canal
    • Texto

Por ejemplo:

Node.js 8 (Beta)

const slack = require('slack');

const BOT_ACCESS_TOKEN = 'xxxx-111111111111-abcdefghidklmnopq';
const CHANNEL = 'general';

exports.notifySlack = async (data, context) => {
  const pubsubMessage = data;
  const pubsubAttrs = JSON.stringify(pubsubMessage.attributes);
  const pubsubData = Buffer.from(pubsubMessage.data, 'base64').toString();
  const budgetNotificationText = `${pubsubAttrs}, ${pubsubData}`;

  const res = await slack.chat.postMessage({
    token: BOT_ACCESS_TOKEN,
    channel: CHANNEL,
    text: budgetNotificationText,
  });
  console.log(res);
};

Python (Beta)

from slackclient import SlackClient

# See https://api.slack.com/docs/token-types#bot for more info
BOT_ACCESS_TOKEN = 'xxxx-111111111111-abcdefghidklmnopq'

CHANNEL_ID = 'C0XXXXXX'

slack_client = SlackClient(BOT_ACCESS_TOKEN)

def notify_slack(data, context):
    pubsub_message = data

    notification_attrs = json.dumps(pubsub_message['attributes'])
    notification_data = base64.b64decode(data['data']).decode('utf-8')
    budget_notification_text = f'{notification_attrs}, {notification_data}'

    slack_client.api_call(
      'chat.postMessage',
      channel=CHANNEL_ID,
      text=budget_notification_text)

Limita (inhabilita) la facturación para detener el uso

En este ejemplo, se muestra cómo limitar los costos y detener el uso para un proyecto de Google Cloud mediante la inhabilitación de la facturación. Eso causará que todos los servicios de Google Cloud cancelen los servicios de nivel no gratuitos para el proyecto.

Puedes limitar los costos debido a que tienes un límite estricto en cuanto al dinero que gastas en Google Cloud Platform. Esto es normal para los estudiantes, investigadores o desarrolladores que trabajan en entornos de zona de pruebas. En esos casos, necesitas detener el gasto y cancelar todos los servicios y el uso de Google Cloud Platform cuando alcanzas el límite de tu presupuesto.

Para nuestro ejemplo, usamos acme-backend-dev como un proyecto de no producción en el que la facturación se puede inhabilitar de manera segura.

Configura el límite de presupuesto en GCP Console

Antes de comenzar, tienes que configurar un presupuesto para supervisar los costos del proyecto y habilitar las notificaciones de presupuesto.

Administra las alertas de facturación en GCP Console

Configura los permisos de la cuenta de servicio

Tu Cloud Function se ejecuta como una cuenta de servicio creada de manera automática. Para que la cuenta de servicio pueda inhabilitar la facturación, tienes que otorgarle permisos de Administrador de facturación.

Para identificar la cuenta de servicio correcta, visualiza los detalles de tu Cloud Function. La cuenta de servicio se encuentra en la parte inferior de la página.

Identifica la cuenta de servicio en GCP Console

Puedes administrar los Billing Admin permissions (Permisos de administrador de facturación) en la Página de facturación de Google Cloud Platform Console.

Para otorgar privilegios de administrador de cuenta de facturación a la cuenta de servicio, selecciona el nombre de la cuenta de servicio.

Administra los permisos en GCP Console

Escribe una Cloud Function

Luego tienes que configurar tu Cloud Function para que llame a la API de Cloud Billing. Eso habilita la Cloud Function para inhabilitar la facturación de tu proyecto de ejemplo acme-backend-dev.

  1. Crea una función nueva con los pasos que se describen en Crea una Cloud Function.

  2. Agrega las dependencias siguientes:

    Node.js 8 (Beta)

    Agrega dependencias a googleapis y google-auth-library para el archivo package.json de la función:
    {
     "name": "cloud-functions-billing",
     "version": "0.0.1",
     "dependencies": {
        "google-auth-library": "^2.0.0",
        "googleapis": "^33.0.0"
     }
    }

    Python (Beta)

    Agrega oauth2client==4.1.3 y google-api-python-client==1.7.4 a tu archivo requirements.txt de la función:
    oauth2client==4.1.3
    google-api-python-client==1.7.4
    

  3. Escribe un código para inhabilitar la facturación en el proyecto y, así, quitarlo de la cuenta de facturación.

  4. Establece el parámetro PROJECT_NAME. Este es el proyecto en el que quieres limitar (inhabilitar) la facturación.

Node.js 8 (Beta)

const {google} = require('googleapis');
const {auth} = require('google-auth-library');

const PROJECT_ID = process.env.GCP_PROJECT;
const PROJECT_NAME = `projects/${PROJECT_ID}`;
const compute = google.compute('v1');
const ZONE = 'us-west1-b';

exports.limitUse = async (data, context) => {
  const pubsubData = JSON.parse(Buffer.from(data.data, 'base64').toString());
  if (pubsubData.costAmount <= pubsubData.budgetAmount) {
    return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
  }

  await _setAuthCredential();
  const instanceNames = await _listRunningInstances(PROJECT_ID, ZONE);
  await _stopInstances(PROJECT_ID, ZONE, instanceNames);
};

/**
 * @return {Promise} Array of names of running instances
 */
const _listRunningInstances = async (projectId, zone) => {
  const res = await compute.instances.list({
    project: projectId,
    zone: zone,
  });

  const instances = res.data.items || [];
  const ranInstances = instances.filter(item => item.status === 'RUNNING');
  return ranInstances.map(item => item.name);
};

/**
 * @param {Array} instanceNames Names of instance to stop
 * @return {Promise} Response from stopping instances
 */
const _stopInstances = async (projectId, zone, instanceNames) => {
  if (!instanceNames.length) {
    return 'No running instances were found.';
  }
  await Promise.all(
    instanceNames.map(instanceName => {
      return compute.instances
        .stop({
          project: projectId,
          zone: zone,
          instance: instanceName,
        })
        .then(res => {
          console.log('Instance stopped successfully: ' + instanceName);
          return res.data;
        });
    })
  );
};

Python (Beta)

import base64
import json
import os
from googleapiclient import discovery
from oauth2client.client import GoogleCredentials

PROJECT_ID = os.getenv('GCP_PROJECT')
PROJECT_NAME = f'projects/{PROJECT_ID}'
def stop_billing(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

    billing = discovery.build(
        'cloudbilling',
        'v1',
        cache_discovery=False,
        credentials=GoogleCredentials.get_application_default()
    )

    projects = billing.projects()

    if __is_billing_enabled(PROJECT_NAME, projects):
        print(__disable_billing_for_project(PROJECT_NAME, projects))
    else:
        print('Billing already disabled')

def __is_billing_enabled(project_name, projects):
    """
    Determine whether billing is enabled for a project
    @param {string} project_name Name of project to check if billing is enabled
    @return {bool} Whether project has billing enabled or not
    """
    res = projects.getBillingInfo(name=project_name).execute()
    return res['billingEnabled']

def __disable_billing_for_project(project_name, projects):
    """
    Disable billing for a project by removing its billing account
    @param {string} project_name Name of project disable billing on
    @return {string} Text containing response from disabling billing
    """
    body = {'billingAccountName': ''}  # Disable billing
    res = projects.updateBillingInfo(name=project_name, body=body).execute()
    print(f'Billing disabled: {json.dumps(res)}')

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,
        credentials=GoogleCredentials.get_application_default()
    )
    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()

    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}')

Valida que la facturación está inhabilitada

Cuando se activa la función, puedes validar que la Cloud Function tuvo éxito. El proyecto ya no estará visible en la cuenta de facturación y los recursos del proyecto se inhabilitarán.

Valida que la facturación está inhabilitada

Puedes volver a habilitar la facturación de manera manual para tu proyecto en la GCP Console.

Controla el uso de manera selectiva

La limitación (inhabilitar la facturación) como se la describe en el ejemplo previo es binaria y terminal. Tu proyecto está habilitado o inhabilitado. Cuando está inhabilitado, todos los servicios se detienen y todos los recursos se borran de manera eventual.

Si requieres una respuesta más matizada, puedes controlar los recursos de manera selectiva. Por ejemplo, si quieres detener algunos recursos de Compute, pero dejar Storage intacto, puedes controlar el uso de manera selectiva. Eso disminuirá tu costo por hora sin inhabilitar por completo tu entorno.

Puedes escribir una política matizada a tu gusto. Sin embargo, para nuestro ejemplo, el proyecto ejecuta una investigación con una cantidad de máquinas virtuales de Compute y almacena los resultados en Cloud Storage. En este ejemplo de Cloud Function, se cancelarán todas las instancias de Compute, pero no se afectarán los resultados almacenados después de que se exceda el presupuesto.

Configura los permisos de la cuenta de servicio

  1. Tu Cloud Function se ejecuta como una cuenta de servicio creada de manera automática. Para controlar el uso tienes que otorgar los permisos de la cuenta de servicio a cualquier servicio del proyecto que necesite hacer cambios.
  2. Para identificar la cuenta de servicio correcta, visualiza los detalles de tu Cloud Function. La cuenta de servicio se encuentra en la parte inferior de la página.
  3. Dirígete a la página de IAM en GCP Console para establecer los permisos correctos.
     
    Administra las alertas de facturación en GCP Console

Escribe una Cloud Function

  1. Crea una función nueva con los pasos que se describen en Crea una Cloud Function.

  2. Asegúrate de que agregaste las dependencias que se describen en Limita (inhabilita) la facturación para detener el uso.

  3. Escribe un código para cancelar los recursos en el proyecto.

  4. Establece el parámetro PROJECT_NAME. Este es el proyecto que querías configurar para la limitación.

Node.js 8 (Beta)

const {google} = require('googleapis');
const {auth} = require('google-auth-library');

const PROJECT_ID = process.env.GCP_PROJECT;
const PROJECT_NAME = `projects/${PROJECT_ID}`;
const billing = google.cloudbilling('v1').projects;

exports.stopBilling = async (data, context) => {
  const pubsubData = JSON.parse(Buffer.from(data.data, 'base64').toString());
  if (pubsubData.costAmount <= pubsubData.budgetAmount) {
    return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
  }

  await _setAuthCredential();
  if (await _isBillingEnabled(PROJECT_NAME)) {
    return _disableBillingForProject(PROJECT_NAME);
  } else {
    return 'Billing already disabled';
  }
};

/**
 * @return {Promise} Credentials set globally
 */
const _setAuthCredential = async () => {
  const res = await auth.getApplicationDefault();

  let client = res.credential;
  if (client.hasScopes && !client.hasScopes()) {
    client = client.createScoped([
      'https://www.googleapis.com/auth/cloud-billing',
      'https://www.googleapis.com/auth/cloud-platform',
    ]);
  }

  // Set credential globally for all requests
  google.options({
    auth: client,
  });
};

/**
 * Determine whether billing is enabled for a project
 * @param {string} projectName Name of project to check if billing is enabled
 * @return {bool} Whether project has billing enabled or not
 */
const _isBillingEnabled = async projectName => {
  const res = await billing.getBillingInfo({name: projectName});
  return res.data.billingEnabled;
};

/**
 * Disable billing for a project by removing its billing account
 * @param {string} projectName Name of project disable billing on
 * @return {string} Text containing response from disabling billing
 */
const _disableBillingForProject = async projectName => {
  const res = await billing.updateBillingInfo({
    name: projectName,
    resource: {billingAccountName: ''}, // Disable billing
  });
  return `Billing disabled: ${JSON.stringify(res.data)}`;
};

Python (Beta)

import base64
import json
from googleapiclient import discovery
from oauth2client.client import GoogleCredentials

PROJECT_ID = os.getenv('GCP_PROJECT')
PROJECT_NAME = f'projects/{PROJECT_ID}'

Valida que la facturación está inhabilitada

Puedes validar que la función se ejecute con éxito si verificas que tus máquinas virtuales de Compute se detuvieron en GCP Console.

¿Te sirvió esta página? Envíanos tu opinión: