Cómo conectarse a Cloud SQL

Descripción general

Cloud SQL es un servicio de base de datos completamente administrado que facilita la configuración, el mantenimiento y la administración de bases de datos relacionales de PostgreSQL y MySQL en la nube. Puedes conectar tus funciones de Cloud Functions a instancias de Cloud SQL con una interfaz de socket local proporcionada en el entorno de ejecución de Cloud Functions. Esta es una conexión privada a tus instancias de Cloud SQL, lo que significa que no necesitas exponerlas a ninguna red externa.

Cómo configurar Cloud SQL

Cloud Functions admite instancias de MySQL de segunda generación, además de instancias de PostgreSQL. Consulta la documentación de Cloud SQL relevante para obtener instrucciones sobre cómo configurar una instancia nueva de Cloud SQL.

Interregión

Cloud Functions puede conectarse a instancias de MySQL de segunda generación y de PostgreSQL en cualquier región. No es necesario que la función y la instancia se encuentren en la misma región.

Interproyecto

Tu función de Cloud Functions tiene acceso a todas las instancias de Cloud SQL del proyecto. Puedes acceder a las instancias de MySQL de segunda generación, así como a las de PostgreSQL en otros proyectos si la cuenta de servicio de tu función de Cloud Functions (que aparece en la pestaña General de la función de Cloud Functions en GCP Console) se agrega como un miembro de IAM en el proyecto con las instancias de Cloud SQL que tengan el rol de Cliente de Cloud SQL.

Conéctate a Cloud SQL

Cloud Functions ofrece una interfaz de socket local para conectar instancias de Cloud SQL, que funcionan para instancias de MySQL y PostgreSQL. Para conectar tu instancia de Cloud SQL puedes usar la ruta de sistema de archivos /cloudsql.

MySQL

Node.js

const mysql = require('mysql');

/**
 * TODO(developer): specify SQL connection details
 */
const connectionName =
  process.env.INSTANCE_CONNECTION_NAME || '<YOUR INSTANCE CONNECTION NAME>';
const dbUser = process.env.SQL_USER || '<YOUR DB USER>';
const dbPassword = process.env.SQL_PASSWORD || '<YOUR DB PASSWORD>';
const dbName = process.env.SQL_NAME || '<YOUR DB NAME>';

const mysqlConfig = {
  connectionLimit: 1,
  user: dbUser,
  password: dbPassword,
  database: dbName,
};
if (process.env.NODE_ENV === 'production') {
  mysqlConfig.socketPath = `/cloudsql/${connectionName}`;
}

// Connection pools reuse connections between invocations,
// and handle dropped or expired connections automatically.
let mysqlPool;

exports.mysqlDemo = (req, res) => {
  // Initialize the pool lazily, in case SQL access isn't needed for this
  // GCF instance. Doing so minimizes the number of active SQL connections,
  // which helps keep your GCF instances under SQL connection limits.
  if (!mysqlPool) {
    mysqlPool = mysql.createPool(mysqlConfig);
  }

  mysqlPool.query('SELECT NOW() AS now', (err, results) => {
    if (err) {
      console.error(err);
      res.status(500).send(err);
    } else {
      res.send(JSON.stringify(results));
    }
  });

  // Close any SQL resources that were declared inside this function.
  // Keep any declared in global scope (e.g. mysqlPool) for later reuse.
};

Python

from os import getenv

import pymysql
from pymysql.err import OperationalError

# TODO(developer): specify SQL connection details
CONNECTION_NAME = getenv(
  'INSTANCE_CONNECTION_NAME',
  '<YOUR INSTANCE CONNECTION NAME>')
DB_USER = getenv('MYSQL_USER', '<YOUR DB USER>')
DB_PASSWORD = getenv('MYSQL_PASSWORD', '<YOUR DB PASSWORD>')
DB_NAME = getenv('MYSQL_DATABASE', '<YOUR DB NAME>')

mysql_config = {
  'user': DB_USER,
  'password': DB_PASSWORD,
  'db': DB_NAME,
  'charset': 'utf8mb4',
  'cursorclass': pymysql.cursors.DictCursor,
  'autocommit': True
}

# Create SQL connection globally to enable reuse
# PyMySQL does not include support for connection pooling
mysql_conn = None

def __get_cursor():
    """
    Helper function to get a cursor
      PyMySQL does NOT automatically reconnect,
      so we must reconnect explicitly using ping()
    """
    try:
        return mysql_conn.cursor()
    except OperationalError:
        mysql_conn.ping(reconnect=True)
        return mysql_conn.cursor()

def mysql_demo(request):
    global mysql_conn

    # Initialize connections lazily, in case SQL access isn't needed for this
    # GCF instance. Doing so minimizes the number of active SQL connections,
    # which helps keep your GCF instances under SQL connection limits.
    if not mysql_conn:
        try:
            mysql_conn = pymysql.connect(**mysql_config)
        except OperationalError:
            # If production settings fail, use local development ones
            mysql_config['unix_socket'] = f'/cloudsql/{CONNECTION_NAME}'
            mysql_conn = pymysql.connect(**mysql_config)

    # Remember to close SQL resources declared while running this function.
    # Keep any declared in global scope (e.g. mysql_conn) for later reuse.
    with __get_cursor() as cursor:
        cursor.execute('SELECT NOW() as now')
        results = cursor.fetchone()
        return str(results['now'])

Go


// Package sql contains examples of using Cloud SQL.
package sql

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"os"

	// Import the MySQL SQL driver.
	_ "github.com/go-sql-driver/mysql"
)

var (
	db *sql.DB

	connectionName = os.Getenv("MYSQL_INSTANCE_CONNECTION_NAME")
	dbUser         = os.Getenv("MYSQL_USER")
	dbPassword     = os.Getenv("MYSQL_PASSWORD")
	dsn            = fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/", dbUser, dbPassword, connectionName)
)

func init() {
	var err error
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		log.Fatalf("Could not open db: %v", err)
	}

	// Only allow 1 connection to the database to avoid overloading it.
	db.SetMaxIdleConns(1)
	db.SetMaxOpenConns(1)
}

// MySQLDemo is an example of making a MySQL database query.
func MySQLDemo(w http.ResponseWriter, r *http.Request) {
	rows, err := db.Query("SELECT NOW() as now")
	if err != nil {
		log.Printf("db.Query: %v", err)
		http.Error(w, "Error querying database", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	now := ""
	rows.Next()
	if err := rows.Scan(&now); err != nil {
		log.Printf("rows.Scan: %v", err)
		http.Error(w, "Error scanning database", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Now: %v", now)
}

PostgreSQL

Node.js

const pg = require('pg');

/**
 * TODO(developer): specify SQL connection details
 */
const connectionName =
  process.env.INSTANCE_CONNECTION_NAME || '<YOUR INSTANCE CONNECTION NAME>';
const dbUser = process.env.SQL_USER || '<YOUR DB USER>';
const dbPassword = process.env.SQL_PASSWORD || '<YOUR DB PASSWORD>';
const dbName = process.env.SQL_NAME || '<YOUR DB NAME>';

const pgConfig = {
  max: 1,
  user: dbUser,
  password: dbPassword,
  database: dbName,
};

if (process.env.NODE_ENV === 'production') {
  pgConfig.host = `/cloudsql/${connectionName}`;
}

// Connection pools reuse connections between invocations,
// and handle dropped or expired connections automatically.
let pgPool;

exports.postgresDemo = (req, res) => {
  // Initialize the pool lazily, in case SQL access isn't needed for this
  // GCF instance. Doing so minimizes the number of active SQL connections,
  // which helps keep your GCF instances under SQL connection limits.
  if (!pgPool) {
    pgPool = new pg.Pool(pgConfig);
  }

  pgPool.query('SELECT NOW() as now', (err, results) => {
    if (err) {
      console.error(err);
      res.status(500).send(err);
    } else {
      res.send(JSON.stringify(results));
    }
  });

  // Close any SQL resources that were declared inside this function.
  // Keep any declared in global scope (e.g. mysqlPool) for later reuse.
};

Python

from os import getenv

from psycopg2 import OperationalError
from psycopg2.pool import SimpleConnectionPool

# TODO(developer): specify SQL connection details
CONNECTION_NAME = getenv(
  'INSTANCE_CONNECTION_NAME',
  '<YOUR INSTANCE CONNECTION NAME>')
DB_USER = getenv('POSTGRES_USER', '<YOUR DB USER>')
DB_PASSWORD = getenv('POSTGRES_PASSWORD', '<YOUR DB PASSWORD>')
DB_NAME = getenv('POSTGRES_DATABASE', '<YOUR DB NAME>')

pg_config = {
  'user': DB_USER,
  'password': DB_PASSWORD,
  'dbname': DB_NAME
}

# Connection pools reuse connections between invocations,
# and handle dropped or expired connections automatically.
pg_pool = None

def __connect(host):
    """
    Helper function to connect to Postgres
    """
    global pg_pool
    pg_config['host'] = host
    pg_pool = SimpleConnectionPool(1, 1, **pg_config)

def postgres_demo(request):
    global pg_pool

    # Initialize the pool lazily, in case SQL access isn't needed for this
    # GCF instance. Doing so minimizes the number of active SQL connections,
    # which helps keep your GCF instances under SQL connection limits.
    if not pg_pool:
        try:
            __connect(f'/cloudsql/{CONNECTION_NAME}')
        except OperationalError:
            # If production settings fail, use local development ones
            __connect('localhost')

    # Remember to close SQL resources declared while running this function.
    # Keep any declared in global scope (e.g. pg_pool) for later reuse.
    with pg_pool.getconn() as conn:
        cursor = conn.cursor()
        cursor.execute('SELECT NOW() as now')
        results = cursor.fetchone()
        pg_pool.putconn(conn)
        return str(results[0])

Go


// Package sql contains examples of using to Cloud SQL.
package sql

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"os"

	// Import the Postgres SQL driver.
	_ "github.com/lib/pq"
)

var (
	db *sql.DB

	connectionName = os.Getenv("POSTGRES_INSTANCE_CONNECTION_NAME")
	dbUser         = os.Getenv("POSTGRES_USER")
	dbPassword     = os.Getenv("POSTGRES_PASSWORD")
	dsn            = fmt.Sprintf("user=%s password=%s host=/cloudsql/%s", dbUser, dbPassword, connectionName)
)

func init() {
	var err error
	db, err = sql.Open("postgres", dsn)
	if err != nil {
		log.Fatalf("Could not open db: %v", err)
	}

	// Only allow 1 connection to the database to avoid overloading it.
	db.SetMaxIdleConns(1)
	db.SetMaxOpenConns(1)
}

// PostgresDemo is an example of making a Postgres database query.
func PostgresDemo(w http.ResponseWriter, r *http.Request) {
	rows, err := db.Query("SELECT NOW() as now")
	if err != nil {
		log.Printf("db.Query: %v", err)
		http.Error(w, "Error querying database", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	now := ""
	rows.Next()
	if err := rows.Scan(&now); err != nil {
		log.Printf("rows.Scan: %v", err)
		http.Error(w, "Error scanning database", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Now: %v", now)
}

Ten en cuenta que PostgreSQL requiere un sufijo .s.PGSQL.5432 en la string de conexión. Las bibliotecas como pg lo agregan automáticamente. Sin embargo, si eliges una biblioteca alternativa, asegúrate de confirmar que se agregó. La string de conexión completa para PostgreSQL cuando usas rutas de socket es la siguiente:

/cloudsql/INSTANCE_CONNECTION_NAME/.s.PGSQL.5432

El INSTANCE_CONNECTION_NAME es el nombre de la conexión de tu instancia de Cloud SQL y, generalmente, tiene el siguiente formato:

ProjectID:Region:InstanceID

Por ejemplo:

my-gcp-project:us-central1:helloworld

Puedes determinar el nombre de la conexión de la instancia desde el panel de Cloud SQL en GCP Console. Ten en cuenta que el “Nombre de conexión de instancia” (que se usa antes) no es el mismo que el del “ID de instancia”.

Recomendaciones para Cloud SQL en Cloud Functions

En esta sección, se proporcionan lineamientos para las conexiones a Cloud SQL en Cloud Functions.

Grupos de conexión y pérdida de la conexión

Las conexiones a bases de datos subyacentes pueden caerse debido al propio servidor de la base de datos o a la infraestructura subyacente a Cloud Functions. Para mitigar este problema, te recomendamos que uses una biblioteca cliente que admita grupos de conexión que reconecten automáticamente las conexiones interrumpidas de clientes.

Cuando se usa un grupo de conexiones, es importante configurar la cantidad máxima de conexiones en 1. Esto puede resultar contraintuitivo, pero crear más de una conexión simultánea por instancia de función puede causar un agotamiento rápido de los recursos de conexión (consulta Cantidad máxima de conexiones simultáneas a continuación para obtener más detalles). Cloud Functions limita las ejecuciones simultáneas a 1 por instancia. Esto significa que nunca enfrentarás una situación en la que una sola instancia de función procese dos solicitudes al mismo tiempo, por lo que en la mayoría de los casos solo se requiere una sola conexión de base de datos.

Reutilización de la conexión

Cuando sea posible, te recomendamos que asignes objetos de conexión de alcance global. Esto representa tanto una optimización del rendimiento como un enfoque menos propenso a errores, ya que a menudo ocurre que el cierre de la conexión se pierde (por ejemplo, cuando se produce un error inesperado).

Si usas un grupo de conexión de alcance global, te recomendamos que no cierres las conexiones al final de la llamada a la función. Si no usas un grupo de alcance global, o si creas conexiones individuales según el alcance de la función, deberías cerrar estas conexiones antes de que la función regrese.

Se recomienda el uso de un grupo de conexión de alcance global, ya que esto mejora la probabilidad de que la misma conexión se reutilice para invocaciones posteriores de la función, y la conexión se cerrará naturalmente cuando la instancia de función sea expulsada (ajuste de escala automático hacia abajo).

Cantidad máxima de conexiones simultáneas

Las ediciones de MySQL y PostgreSQL de Cloud SQL imponen un límite máximo en la cantidad de conexiones simultáneas, y estos límites pueden variar según el motor de la base de datos elegida (consulta la página Cuotas y límites de Cloud SQL). Si cada una de las instancias de función crea una conexión, tu capacidad de procesamiento general estará limitada por la cantidad de conexiones simultáneas que se pueden crear. Cuando sea posible, solo deberías abrir una conexión si la función necesita usarla. Un antipatrón común aquí es implementar una función que retiene una referencia de una conexión de escala global (a menudo considerado una recomendación), pero que posteriormente no usa la conexión en ningún momento (antipatrón).

Solución de problemas

Cómo conectarse a instancias de Cloud SQL de primera generación

La segunda generación de Cloud SQL está reemplazando a la primera. Cloud Functions no admite la conexión a instancias de primera generación con /cloudsql. Si utilizas una instancia de Cloud SQL de primera generación, recomendamos que actualices a la segunda generación.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Functions