התחברות מ-Cloud Run

בדף הזה מופיעים מידע ודוגמאות להתחברות למופע Cloud SQL משירות שפועל ב-Cloud Run.

הוראות מפורטות להפעלת אפליקציית אינטרנט לדוגמה ב-Cloud Run שמחוברת ל-Cloud SQL זמינות במדריך למתחילים בנושא חיבור מ-Cloud Run.

‫Cloud SQL הוא שירות מנוהל של מסד נתונים, שבעזרתו אפשר ליצור, לתחזק ולנהל מסדי נתונים רלציוניים בענן.

Cloud Run היא פלטפורמת מחשוב מנוהלת שמאפשרת להריץ קונטיינרים ישירות על גבי תשתית Google Cloud .

הגדרת מופע של Cloud SQL

  1. אם עדיין לא עשיתם את זה, מפעילים את Cloud SQL Admin API בפרויקט Google Cloud שממנו מתחברים:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את תפקיד ה-IAM 'אדמין של Service Usage' (roles/serviceusage.serviceUsageAdmin), שכולל את ההרשאה serviceusage.services.enable. איך מקצים תפקידים

    להפעלת ה-API

  2. יצירת מכונה של Cloud SQL ל-SQL Server. מומלץ לבחור מיקום של מופע Cloud SQL באותו אזור שבו נמצא שירות Cloud Run, כדי לשפר את זמן האחזור, להימנע מחלק מהעלויות של הרשת ולהפחית את הסיכון לכשלים חוצי-אזורים.

    כברירת מחדל, Cloud SQL מקצה כתובת IP ציבורית למכונה חדשה. יש לכם גם אפשרות להקצות כתובת IP פרטית. מידע נוסף על אפשרויות הקישוריות לשניהם זמין בדף סקירה כללית על חיבורים.

  3. כשיוצרים את המכונה, אפשר לבחור את היררכיית אישורי השרת (CA) עבור המכונה, ואז להגדיר את ההיררכיה כ-serverCaMode עבור המכונה. צריך לבחור באפשרות של CA לכל מופע (GOOGLE_MANAGED_INTERNAL_CA) בתור מצב ה-CA של השרת עבור מופעים שרוצים להתחבר אליהם מאפליקציות אינטרנט.

הגדרת Cloud Run

השלבים להגדרת Cloud Run תלויים בסוג כתובת ה-IP שהקציתם למכונה של Cloud SQL. אם אתם מעבירים את כל תעבורת הנתונים היוצאת (egress) דרך תעבורת נתונים יוצאת (egress) ישירה של VPC או דרך מחבר של חיבור לרשת (VPC) מאפליקציית serverless, אתם צריכים להשתמש בכתובת IP פרטית. השוואה בין יציאה ישירה מ-VPC לבין מחברי VPC

כתובת IP ציבורית (ברירת מחדל)

‫Cloud Run תומך בחיבור ל-Cloud SQL ל-SQL Server דרך כתובת IP ציבורית באמצעות מחברי Go, ‏ Java ו-Python.

  • מוודאים שלמופע יש כתובת IP ציבורית. אפשר לבדוק את זה בדף Overview של המופע במסוףGoogle Cloud . אם אתם צריכים להוסיף כתובת IP ציבורית, תוכלו להיעזר בהוראות שבמאמר הגדרת כתובת IP ציבורית.
  • מאחזרים את INSTANCE_CONNECTION_NAME של המופע. אפשר למצוא את הערך הזה בדף Overview של המופע בGoogle Cloud מסוף או על ידי הפעלת הפקודה הבאה: gcloud sql instances describe
    gcloud sql instances describe INSTANCE_NAME
       
    מחליפים את INSTANCE_NAME בשם של מופע Cloud SQL.
  • מקבלים את CLOUD_RUN_SERVICE_ACCOUNT_NAME של שירות Cloud Run. אפשר למצוא את הערך הזה בדף IAM של הפרויקט שמארח את שירות Cloud Run בGoogle Cloud מסוף, או על ידי הפעלת הפקודה gcloud run services describe הבאה בפרויקט שמארח את שירות Cloud Run:
    gcloud run services describe CLOUD_RUN_SERVICE_NAME
    --region CLOUD_RUN_SERVICE_REGION --format="value(spec.template.spec.serviceAccountName)"
       
    מחליפים את המשתנים הבאים:
    • CLOUD_RUN_SERVICE_NAME: השם של שירות Cloud Run
    • CLOUD_RUN_SERVICE_REGION: האזור של שירות Cloud Run
  • מגדירים את חשבון השירות בשביל שירות Cloud Run. כדי להתחבר ל-Cloud SQL, צריך לוודא שלחשבון השירות יש Cloud SQL Client תפקיד IAM.
  • אם מוסיפים חיבור ל-Cloud SQL לשירות חדש, צריך להוסיף את השירות לקונטיינר ולהעלות אותו ל-Container Registry או ל-Artifact Registry. אם עדיין אין לכם חיבור, תוכלו לעיין בהוראות האלה בנושא יצירה ופריסה של קובץ אימג' של קונטיינר.
  • אם אתם מתחברים למופעים שהוגדרו עם האפשרות של רשות אישורים (CA) משותפת (GOOGLE_MANAGED_CAS_CA) או עם האפשרות של CA בניהול הלקוח (CUSTOMER_MANAGED_CAS_CA) בתור מצב CA של השרת, אתם צריכים לבחור את סביבת ההפעלה מהדור השני כשאתם בוחרים את סביבת ההפעלה של השירות. שתי האפשרויות של מצב CA של השרת מחייבות חיבור למכונה באמצעות Cloud SQL Auth Proxy v2.

    אם השירות פועל בסביבת ביצוע מהדור הראשון, אפשר להתחבר רק למופעי Cloud SQL שהוגדרו עם האפשרות של רשות אישורי בסיס (CA) לכל מופע (GOOGLE_MANAGED_INTERNAL_CA) כמצב CA של השרת. סביבת ההפעלה של הדור הראשון של Cloud Run כוללת את שרת ה-proxy ל-Cloud SQL Auth v1. מידע נוסף על דרישות החיבור ל-Cloud SQL עבור שרת ה-proxy ל-Cloud SQL Auth זמין במאמר דרישות לשימוש בשרת ה-proxy ל-Cloud SQL Auth.

בדומה לכל שינוי בהגדרות, הגדרה חדשה של החיבור ל-Cloud SQL מובילה ליצירה של גרסה חדשה ב-Cloud Run. גם גרסאות עתידיות יקבלו אוטומטית את החיבור הזה ל-Cloud SQL, אלא אם תבצעו עדכונים מפורשים כדי לשנות אותו.

המסוף

  1. כניסה ל-Cloud Run

  2. מתחילים להגדיר את השירות. כדי להוסיף חיבורים ל-Cloud SQL לשירות קיים:

    1. ברשימה שירותים, לוחצים על שם השירות הרצוי.
    2. לוחצים על עריכה ופריסה של גרסה חדשה.
  3. מפעילים את האפשרות להתחבר למכונת Cloud SQL:
    1. לוחצים על מאגרי תגים ואז על הגדרות.
    2. גוללים אל חיבורים ל-Cloud SQL.
    3. לוחצים על הוספת חיבור.
    4. אם עדיין לא הפעלתם את Cloud SQL Admin API, לוחצים על הלחצן Enable the Cloud SQL Admin.

    הוספת חיבור ל-Cloud SQL

    • אם מוסיפים חיבור למופע Cloud SQL בפרויקט, בוחרים את מופע Cloud SQL הרצוי מהתפריט.
    • אם אתם משתמשים במופע Cloud SQL מפרויקט אחר, צריך לבחור באפשרות מחרוזת חיבור בהתאמה אישית בתפריט ולהזין את שם החיבור המלא של המופע בפורמט PROJECT-ID:REGION:INSTANCE-ID.
    • כדי למחוק קישור, מעבירים את העכבר לצד שמאל של הקישור כדי להציג את הסמל מחיקה ולוחצים עליו.
  4. לוחצים על יצירה או על פריסה.

שורת הפקודה

לפני שמשתמשים באחת מהפקודות הבאות, צריך להחליף את המשתנים הבאים בערכים:

  • IMAGE עם התמונה שאתם פורסים
  • SERVICE_NAME בשם של שירות Cloud Run
  • INSTANCE_CONNECTION_NAME עם שם החיבור של המכונה של Cloud SQL, או רשימה של שמות חיבורים מופרדים בפסיקים.

    אם אתם פורסים קונטיינר חדש, משתמשים בפקודה הבאה:

    gcloud run deploy \
      --image=IMAGE \
      --add-cloudsql-instances=INSTANCE_CONNECTION_NAME
    אם מעדכנים שירות קיים, משתמשים בפקודה הבאה:
    gcloud run services update SERVICE_NAME \
      --add-cloudsql-instances=INSTANCE_CONNECTION_NAME

Terraform

הקוד הבא יוצר קונטיינר בסיסי של Cloud Run עם מכונה מקושרת של Cloud SQL.

resource "google_cloud_run_v2_service" "default" {
  name     = "cloudrun-service"
  location = "us-central1"

  deletion_protection = false # set to "true" in production

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest" # Image to deploy

      volume_mounts {
        name       = "cloudsql"
        mount_path = "/cloudsql"
      }
    }
    volumes {
      name = "cloudsql"
      cloud_sql_instance {
        instances = [google_sql_database_instance.default.connection_name]
      }
    }
  }
  client     = "terraform"
  depends_on = [google_project_service.secretmanager_api, google_project_service.cloudrun_api, google_project_service.sqladmin_api]
}

  1. מזינים terraform apply כדי להחיל את השינויים.
  2. כדי לוודא שהשינויים בוצעו, בודקים את השירות Cloud Run, לוחצים על הכרטיסייה Revisions (עדכונים) ואז על הכרטיסייה Connections (חיבורים).

כתובת IP פרטית

אם חשבון השירות שמוגדרים לו הרשאות שייך לפרויקט אחר מזה שמכיל את מכונת Cloud SQL, צריך לבצע את הפעולות הבאות:

  • מפעילים את Cloud SQL Admin API בשני הפרויקטים.
  • מוסיפים את הרשאות ה-IAM לחשבון השירות בפרויקט שמכיל את מכונת Cloud SQL.
יציאה ישירה מ-VPC ומחברים משתמשים בכתובות IP פרטיות כדי לטפל בתקשורת עם רשת ה-VPC. כדי להתחבר ישירות לכתובות IP פרטיות באמצעות אחת משיטות היציאה האלה, צריך לבצע את הפעולות הבאות:
  1. מוודאים שלמכונת Cloud SQL שנוצרה קודם יש כתובת IP פרטית. כדי להוסיף כתובת IP פנימית, אפשר לעיין במאמר בנושא הגדרת כתובת IP פרטית.
  2. מגדירים את שיטת היציאה כדי להתחבר לאותה רשת VPC כמו מכונת Cloud SQL. חשוב לשים לב לתנאים הבאים:
    • גם Direct VPC וגם חיבור לרשת (VPC) מאפליקציית serverless תומכים בתקשורת עם רשתות VPC שמחוברות באמצעות Cloud VPN וקישור בין רשתות שכנות (peering) של VPC.
    • Direct VPC egress ו-Serverless VPC Access לא תומכים ברשתות מדור קודם.
    • אלא אם אתם משתמשים ב- VPC משותף, המחבר חייב להיות באותו פרויקט ובאותו אזור כמו המשאב שמשתמש בו, אבל המחבר יכול לשלוח תעבורה למשאבים באזורים שונים.
  3. מתחברים באמצעות כתובת ה-IP הפרטית והיציאה של המופע 1433.

התחברות ל-Cloud SQL

אחרי שמגדירים את Cloud Run, אפשר להתחבר למכונת Cloud SQL.

כתובת IP ציבורית (ברירת מחדל)

בנתיבי כתובות IP ציבוריות, Cloud Run מספק הצפנה ומתחבר באמצעות מחברי Cloud SQL.

התחברות באמצעות מחברים של Cloud SQL

מחברי Cloud SQL הם ספריות ספציפיות לשפה שמספקות הצפנה והרשאה מבוססת-IAM כשמתחברים למכונת Cloud SQL.

Python

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

import os

from google.cloud.sql.connector import Connector, IPTypes
import pytds

import sqlalchemy


def connect_with_connector() -> sqlalchemy.engine.base.Engine:
    """
    Initializes a connection pool for a Cloud SQL instance of SQL Server.

    Uses the Cloud SQL Python Connector package.
    """
    # Note: Saving credentials in environment variables is convenient, but not
    # secure - consider a more secure solution such as
    # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
    # keep secrets safe.

    instance_connection_name = os.environ[
        "INSTANCE_CONNECTION_NAME"
    ]  # e.g. 'project:region:instance'
    db_user = os.environ.get("DB_USER", "")  # e.g. 'my-db-user'
    db_pass = os.environ["DB_PASS"]  # e.g. 'my-db-password'
    db_name = os.environ["DB_NAME"]  # e.g. 'my-database'

    ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

    # initialize Cloud SQL Python Connector object
    connector = Connector(ip_type=ip_type, refresh_strategy="LAZY")

    connect_args = {}
    # If your SQL Server instance requires SSL, you need to download the CA
    # certificate for your instance and include cafile={path to downloaded
    # certificate} and validate_host=False. This is a workaround for a known issue.
    if os.environ.get("DB_ROOT_CERT"):  # e.g. '/path/to/my/server-ca.pem'
        connect_args = {
            "cafile": os.environ["DB_ROOT_CERT"],
            "validate_host": False,
        }

    def getconn() -> pytds.Connection:
        conn = connector.connect(
            instance_connection_name,
            "pytds",
            user=db_user,
            password=db_pass,
            db=db_name,
            **connect_args
        )
        return conn

    pool = sqlalchemy.create_engine(
        "mssql+pytds://",
        creator=getconn,
        # ...
    )
    return pool

Java

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

הערה:

  • המשתנה CLOUD_SQL_CONNECTION_NAME צריך להיות מיוצג בתור ‎<MY-PROJECT>:<INSTANCE-REGION>:<INSTANCE-NAME>
  • כאן אפשר לראות את דרישות הגרסה של JDBC socket factory עבור הקובץ pom.xml.


import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class ConnectorConnectionPoolFactory extends ConnectionPoolFactory {

  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  private static final String INSTANCE_CONNECTION_NAME =
      System.getenv("INSTANCE_CONNECTION_NAME");
  private static final String DB_USER = System.getenv("DB_USER");
  private static final String DB_PASS = System.getenv("DB_PASS");
  private static final String DB_NAME = System.getenv("DB_NAME");

  public static DataSource createConnectionPool() {
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();

    // The following is equivalent to setting the config options below:
    // jdbc:sqlserver://;user=<DB_USER>;password=<DB_PASS>;databaseName=<DB_NAME>;
    // socketFactoryClass=com.google.cloud.sql.sqlserver.SocketFactory;
    // socketFactoryConstructorArg=<INSTANCE_CONNECTION_NAME>

    // See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
    // https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url

    // Configure which instance and what database user to connect with.
    config
        .setDataSourceClassName("com.microsoft.sqlserver.jdbc.SQLServerDataSource");
    config.setUsername(DB_USER); // e.g. "root", "sqlserver"
    config.setPassword(DB_PASS); // e.g. "my-password"
    config.addDataSourceProperty("databaseName", DB_NAME);

    config.addDataSourceProperty("socketFactoryClass",
        "com.google.cloud.sql.sqlserver.SocketFactory");
    config.addDataSourceProperty("socketFactoryConstructorArg", INSTANCE_CONNECTION_NAME);

    // The Java Connector provides SSL encryption, so it should be disabled
    // at the driver level.
    config.addDataSourceProperty("encrypt", "false");

    // cloudSqlRefreshStrategy set to "lazy" is used to perform a
    // refresh when needed, rather than on a scheduled interval.
    // This is recommended for serverless environments to
    // avoid background refreshes from throttling CPU.
    config.addDataSourceProperty("cloudSqlRefreshStrategy", "lazy");

    // ... Specify additional connection properties here.
    // ...

    // Initialize the connection pool using the configuration object.
    return new HikariDataSource(config);
  }
}

המשך

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

package cloudsql

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

	"cloud.google.com/go/cloudsqlconn"
	mssql "github.com/denisenkom/go-mssqldb"
)

type csqlDialer struct {
	dialer     *cloudsqlconn.Dialer
	connName   string
	usePrivate bool
}

// DialContext adheres to the mssql.Dialer interface.
func (c *csqlDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
	var opts []cloudsqlconn.DialOption
	if c.usePrivate {
		opts = append(opts, cloudsqlconn.WithPrivateIP())
	}
	return c.dialer.Dial(ctx, c.connName, opts...)
}

func connectWithConnector() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Fatal Error in connect_connector.go: %s environment variable not set.\n", k)
		}
		return v
	}
	// Note: Saving credentials in environment variables is convenient, but not
	// secure - consider a more secure solution such as
	// Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
	// keep secrets safe.
	var (
		dbUser                 = mustGetenv("DB_USER")                  // e.g. 'my-db-user'
		dbPwd                  = mustGetenv("DB_PASS")                  // e.g. 'my-db-password'
		dbName                 = mustGetenv("DB_NAME")                  // e.g. 'my-database'
		instanceConnectionName = mustGetenv("INSTANCE_CONNECTION_NAME") // e.g. 'project:region:instance'
		usePrivate             = os.Getenv("PRIVATE_IP")
	)

	dbURI := fmt.Sprintf("user id=%s;password=%s;database=%s;", dbUser, dbPwd, dbName)
	c, err := mssql.NewConnector(dbURI)
	if err != nil {
		return nil, fmt.Errorf("mssql.NewConnector: %w", err)
	}
	// WithLazyRefresh() Option is used to perform refresh
	// when needed, rather than on a scheduled interval.
	// This is recommended for serverless environments to
	// avoid background refreshes from throttling CPU.
	dialer, err := cloudsqlconn.NewDialer(context.Background(), cloudsqlconn.WithLazyRefresh())
	if err != nil {
		return nil, fmt.Errorf("cloudsqlconn.NewDailer: %w", err)
	}
	c.Dialer = &csqlDialer{
		dialer:     dialer,
		connName:   instanceConnectionName,
		usePrivate: usePrivate != "",
	}

	dbPool := sql.OpenDB(c)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}
	return dbPool, nil
}

Node.js

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

const {Connection} = require('tedious');
const {Connector} = require('@google-cloud/cloud-sql-connector');

// In case the PRIVATE_IP environment variable is defined then we set
// the ipType=PRIVATE for the new connector instance, otherwise defaults
// to public ip type.
const getIpType = () =>
  process.env.PRIVATE_IP === '1' || process.env.PRIVATE_IP === 'true'
    ? 'PRIVATE'
    : 'PUBLIC';

// connectWithConnector initializes a TCP connection
// to a Cloud SQL instance of SQL Server.
const connectWithConnector = async config => {
  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  const connector = new Connector();
  const clientOpts = await connector.getTediousOptions({
    instanceConnectionName: process.env.INSTANCE_CONNECTION_NAME,
    ipType: getIpType(),
  });
  const dbConfig = {
    // Please note that the `server` property here is not used and is only
    // defined due to a bug in the tedious driver
    // (ref: https://github.com/tediousjs/tedious/issues/1541)
    // With that in mind, do not try to change this value since it will have no
    // impact in how the connector works, this sample will be updated to remove
    // this property declaration as soon as the tedious driver bug is fixed
    server: '0.0.0.0', // e.g. '127.0.0.1'
    authentication: {
      type: 'default',
      options: {
        userName: process.env.DB_USER, // e.g. 'my-db-user'
        password: process.env.DB_PASS, // e.g. 'my-db-password'
      },
    },
    options: {
      ...clientOpts,
      // Please note that the `port` property here is not used and is only
      // defined due to a bug in the tedious driver
      // (ref: https://github.com/tediousjs/tedious/issues/1541)
      // With that in mind, do not try to change this value since it will have
      // no impact in how the connector works, this sample will be updated to
      // remove this property declaration as soon as the tedious driver bug is
      // fixed
      port: 9999,
      database: process.env.DB_NAME, // e.g. 'my-database'
      useColumnNames: true,
    },
    // ... Specify additional properties here.
    ...config,
  };

  // Establish a connection to the database.
  return new Connection(dbConfig);
};

שימוש ב-Secret Manager

‫Google ממליצה להשתמש ב-Secret Manager כדי לאחסן מידע רגיש כמו פרטי כניסה ל-SQL. אתם יכולים להעביר סודות כמשתני סביבה או לצרף אותם כנפח באמצעות Cloud Run.

אחרי יצירת סוד ב-Secret Manager, מעדכנים שירות קיים באמצעות הפקודה הבאה:

שורת הפקודה

gcloud run services update SERVICE_NAME \
  --add-cloudsql-instances=INSTANCE_CONNECTION_NAME
  --update-env-vars=INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME_SECRET \
  --update-secrets=DB_USER=DB_USER_SECRET:latest \
  --update-secrets=DB_PASS=DB_PASS_SECRET:latest \
  --update-secrets=DB_NAME=DB_NAME_SECRET:latest

Terraform

הפקודה הבאה יוצרת משאבי סודות כדי לאחסן בצורה מאובטחת את הערכים של שם המשתמש, הסיסמה והשם של מסד הנתונים באמצעות google_secret_manager_secret ו-google_secret_manager_secret_version. חשוב לשים לב: צריך לעדכן את חשבון השירות של פרויקט Compute כדי שתהיה לו גישה לכל סוד.


# Create dbuser secret
resource "google_secret_manager_secret" "dbuser" {
  secret_id = "dbusersecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbuser secret
resource "google_secret_manager_secret_version" "dbuser_data" {
  secret      = google_secret_manager_secret.dbuser.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbuser secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbuser" {
  secret_id = google_secret_manager_secret.dbuser.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}


# Create dbpass secret
resource "google_secret_manager_secret" "dbpass" {
  secret_id = "dbpasssecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbpass secret
resource "google_secret_manager_secret_version" "dbpass_data" {
  secret      = google_secret_manager_secret.dbpass.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbpass secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbpass" {
  secret_id = google_secret_manager_secret.dbpass.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}


# Create dbname secret
resource "google_secret_manager_secret" "dbname" {
  secret_id = "dbnamesecret"
  replication {
    auto {}
  }
  depends_on = [google_project_service.secretmanager_api]
}

# Attaches secret data for dbname secret
resource "google_secret_manager_secret_version" "dbname_data" {
  secret      = google_secret_manager_secret.dbname.id
  secret_data = "secret-data" # Stores secret as a plain txt in state
}

# Update service account for dbname secret
resource "google_secret_manager_secret_iam_member" "secretaccess_compute_dbname" {
  secret_id = google_secret_manager_secret.dbname.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" # Project's compute service account
}

מעדכנים את משאב Cloud Run הראשי כך שיכלול את הסודות החדשים.

resource "google_cloud_run_v2_service" "default" {
  name     = "cloudrun-service"
  location = "us-central1"

  deletion_protection = false # set to "true" in production

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest" # Image to deploy

      # Sets a environment variable for instance connection name
      env {
        name  = "INSTANCE_CONNECTION_NAME"
        value = google_sql_database_instance.default.connection_name
      }
      # Sets a secret environment variable for database user secret
      env {
        name = "DB_USER"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbuser.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }
      # Sets a secret environment variable for database password secret
      env {
        name = "DB_PASS"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbpass.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }
      # Sets a secret environment variable for database name secret
      env {
        name = "DB_NAME"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.dbname.secret_id # secret name
            version = "latest"                                      # secret version number or 'latest'
          }
        }
      }

      volume_mounts {
        name       = "cloudsql"
        mount_path = "/cloudsql"
      }
    }
    volumes {
      name = "cloudsql"
      cloud_sql_instance {
        instances = [google_sql_database_instance.default.connection_name]
      }
    }
  }
  client     = "terraform"
  depends_on = [google_project_service.secretmanager_api, google_project_service.cloudrun_api, google_project_service.sqladmin_api]
}

מזינים terraform apply כדי להחיל את השינויים.

בדוגמה לפקודה נעשה שימוש בגרסה הסודית, latest. עם זאת, Google ממליצה להצמיד את הסוד לגרסה ספציפית, SECRET_NAME:v1.

כתובת IP פרטית

בנתיבי IP פרטיים, האפליקציה מתחברת ישירות למופע דרך רשת VPC. השיטה הזו משתמשת ב-TCP כדי להתחבר ישירות למכונה של Cloud SQL בלי להשתמש בשרת ה-proxy ל-Cloud SQL Auth.

חיבור באמצעות TCP

מתחברים באמצעות כתובת ה-IP הפרטית של מופע Cloud SQL כמארח והיציאה 1433.

Python

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

import os

import sqlalchemy


def connect_tcp_socket() -> sqlalchemy.engine.base.Engine:
    """Initializes a TCP connection pool for a Cloud SQL instance of SQL Server."""
    # Note: Saving credentials in environment variables is convenient, but not
    # secure - consider a more secure solution such as
    # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
    # keep secrets safe.
    db_host = os.environ[
        "INSTANCE_HOST"
    ]  # e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
    db_user = os.environ["DB_USER"]  # e.g. 'my-db-user'
    db_pass = os.environ["DB_PASS"]  # e.g. 'my-db-password'
    db_name = os.environ["DB_NAME"]  # e.g. 'my-database'
    db_port = os.environ["DB_PORT"]  # e.g. 1433

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # mssql+pytds://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
        sqlalchemy.engine.url.URL.create(
            drivername="mssql+pytds",
            username=db_user,
            password=db_pass,
            database=db_name,
            host=db_host,
            port=db_port,
        ),
        # ...
    )

    return pool

Java

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

הערה:

  • המשתנה CLOUD_SQL_CONNECTION_NAME צריך להיות מיוצג בתור ‎<MY-PROJECT>:<INSTANCE-REGION>:<INSTANCE-NAME>
  • השימוש בארגומנט ipTypes=PRIVATE יגרום ל-SocketFactory להתחבר עם כתובת IP פרטית שמשויכת למופע.
  • אפשר לעיין בדרישות לגבי גרסת JDBC socket factory לקובץ pom.xml.


import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class TcpConnectionPoolFactory extends ConnectionPoolFactory {

  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  private static final String DB_USER = System.getenv("DB_USER");
  private static final String DB_PASS = System.getenv("DB_PASS");
  private static final String DB_NAME = System.getenv("DB_NAME");

  private static final String INSTANCE_HOST = System.getenv("INSTANCE_HOST");
  private static final String DB_PORT = System.getenv("DB_PORT");


  public static DataSource createConnectionPool() {
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();

    // Configure which instance and what database user to connect with.
    config.setJdbcUrl(
        String.format("jdbc:sqlserver://%s:%s;databaseName=%s", INSTANCE_HOST, DB_PORT, DB_NAME));
    config.setUsername(DB_USER); // e.g. "root", "sqlserver"
    config.setPassword(DB_PASS); // e.g. "my-password"


    // ... Specify additional connection properties here.
    // ...

    // Initialize the connection pool using the configuration object.
    return new HikariDataSource(config);
  }
}

Node.js

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

const mssql = require('mssql');

// createTcpPool initializes a TCP connection pool for a Cloud SQL
// instance of SQL Server.
const createTcpPool = async config => {
  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  const dbConfig = {
    server: process.env.INSTANCE_HOST, // e.g. '127.0.0.1'
    port: parseInt(process.env.DB_PORT), // e.g. 1433
    user: process.env.DB_USER, // e.g. 'my-db-user'
    password: process.env.DB_PASS, // e.g. 'my-db-password'
    database: process.env.DB_NAME, // e.g. 'my-database'
    options: {
      trustServerCertificate: true,
    },
    // ... Specify additional properties here.
    ...config,
  };
  // Establish a connection to the database.
  return mssql.connect(dbConfig);
};

המשך

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

package cloudsql

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"strings"

	_ "github.com/denisenkom/go-mssqldb"
)

// connectTCPSocket initializes a TCP connection pool for a Cloud SQL
// instance of SQL Server.
func connectTCPSocket() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Fatal Error in connect_tcp.go: %s environment variable not set.\n", k)
		}
		return v
	}
	// Note: Saving credentials in environment variables is convenient, but not
	// secure - consider a more secure solution such as
	// Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
	// keep secrets safe.
	var (
		dbUser    = mustGetenv("DB_USER")       // e.g. 'my-db-user'
		dbPwd     = mustGetenv("DB_PASS")       // e.g. 'my-db-password'
		dbTCPHost = mustGetenv("INSTANCE_HOST") // e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
		dbPort    = mustGetenv("DB_PORT")       // e.g. '1433'
		dbName    = mustGetenv("DB_NAME")       // e.g. 'my-database'
	)

	dbURI := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s;",
		dbTCPHost, dbUser, dbPwd, dbPort, dbName)


	// dbPool is the pool of database connections.
	dbPool, err := sql.Open("sqlserver", dbURI)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}

	// ...

	return dbPool, nil
}

Ruby

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

tcp: &tcp
  adapter: sqlserver
  # Configure additional properties here.
  # Note: Saving credentials in environment variables is convenient, but not
  # secure - consider a more secure solution such as
  # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  # keep secrets safe.
  username: <%= ENV["DB_USER"] %>  # e.g. "my-database-user"
  password: <%= ENV["DB_PASS"] %> # e.g. "my-database-password"
  database: <%= ENV.fetch("DB_NAME") { "vote_development" } %>
  host: <%= ENV.fetch("INSTANCE_HOST") { "127.0.0.1" }%> # '172.17.0.1' if deployed to GAE Flex
  port: <%= ENV.fetch("DB_PORT") { 1433 }%> 

PHP

כדי לראות את קטע הקוד הזה בהקשר של אפליקציית אינטרנט, אפשר לעיין בקובץ ה-README ב-GitHub.

namespace Google\Cloud\Samples\CloudSQL\SQLServer;

use PDO;
use PDOException;
use RuntimeException;
use TypeError;

class DatabaseTcp
{
    public static function initTcpDatabaseConnection(): PDO
    {
        try {
            // Note: Saving credentials in environment variables is convenient, but not
            // secure - consider a more secure solution such as
            // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
            // keep secrets safe.
            $username = getenv('DB_USER'); // e.g. 'your_db_user'
            $password = getenv('DB_PASS'); // e.g. 'your_db_password'
            $dbName = getenv('DB_NAME'); // e.g. 'your_db_name'
            $instanceHost = getenv('INSTANCE_HOST'); // e.g. '127.0.0.1' ('172.17.0.1' for GAE Flex)

            // Connect using TCP
            $dsn = sprintf(
                'sqlsrv:server=%s;Database=%s',
                $instanceHost,
                $dbName
            );

            // Connect to the database
            $conn = new PDO(
                $dsn,
                $username,
                $password,
                # ...
            );
        } catch (TypeError $e) {
            throw new RuntimeException(
                sprintf(
                    'Invalid or missing configuration! Make sure you have set ' .
                        '$username, $password, $dbName, and $instanceHost (for TCP mode). ' .
                        'The PHP error was %s',
                    $e->getMessage()
                ),
                $e->getCode(),
                $e
            );
        } catch (PDOException $e) {
            throw new RuntimeException(
                sprintf(
                    'Could not connect to the Cloud SQL Database. Check that ' .
                        'your username and password are correct, that the Cloud SQL ' .
                        'proxy is running, and that the database exists and is ready ' .
                        'for use. For more assistance, refer to %s. The PDO error was %s',
                    'https://cloud.google.com/sql/docs/sqlserver/connect-external-app',
                    $e->getMessage()
                ),
                (int) $e->getCode(),
                $e
            );
        }

        return $conn;
    }
}

שיטות מומלצות ומידע נוסף

אתם יכולים להשתמש בשרת proxy ל-Cloud SQL Auth כשאתם בודקים את האפליקציה באופן מקומי. הוראות מפורטות מופיעות במדריך לתחילת העבודה עם שרת proxy ל-Cloud SQL Auth.

אפשר גם לבדוק באמצעות Cloud SQL Proxy דרך קונטיינר Docker.

מאגרי חיבורים

יכול להיות שהחיבורים למסדי הנתונים הבסיסיים ינותקו, על ידי שרת מסד הנתונים עצמו או על ידי התשתית של הפלטפורמה. מומלץ להשתמש בספריית לקוח שתומכת במאגרי חיבורים שמבצעים חיבור מחדש אוטומטי לחיבורי לקוח שנפסקו. אתם יכולים להשתמש בניהול מאגר חיבורים עם מכונות Cloud SQL, כדי לשנות את גודל עומסי העבודה על ידי אופטימיזציה של ניצול המשאבים ושל זמן האחזור של החיבורים למכונות Cloud SQL באמצעות מאגר חיבורים. מידע מפורט על ניהול מאגר חיבורים זמין במאמר סקירה כללית על ניהול מאגר חיבורים.

דוגמאות מפורטות נוספות לשימוש במאגרי חיבורים זמינות בדף ניהול חיבורים למסד נתונים.

מגבלות על חיבורים

גם ב-MySQL וגם ב-PostgreSQL של Cloud SQL יש מגבלה מקסימלית על מספר החיבורים בו-זמנית, והמגבלות האלה עשויות להשתנות בהתאם למנוע מסד הנתונים שנבחר (מידע נוסף זמין בדף מכסות ומגבלות של Cloud SQL).

מספר החיבורים המקסימלי למסד נתונים של Cloud SQL במכונות קונטיינר של Cloud Run הוא 100. לכל מופע של שירות או משימה ב-Cloud Run יכולים להיות 100 חיבורים למסד הנתונים, וככל שהשירות או המשימה גדלים, המספר הכולל של החיבורים לכל פריסה יכול לגדול.

אתם יכולים להגביל את המספר המקסימלי של חיבורים שמשמשים כל מכונה באמצעות מאגר חיבורים. דוגמאות מפורטות נוספות להגבלת מספר החיבורים מופיעות בדף ניהול חיבורים למסד נתונים.

מגבלות מכסת API

‫Cloud Run מספק מנגנון שמתחבר באמצעות שרת proxy ל-Cloud SQL Auth, שמשתמש ב-Cloud SQL Admin API. מגבלות המכסה של API חלות על שרת proxy ל-Cloud SQL Auth. המיכסה של Cloud SQL Admin API שנמצאת בשימוש היא בערך פי שניים ממספר המכונות של Cloud SQL שהוגדרו, כפול מספר המכונות של Cloud Run של שירות מסוים שנפרס בכל זמן נתון. כדי לשנות את מכסת ה-API הצפויה, אפשר להגביל את מספר המופעים של Cloud Run או להגדיל אותו.

המאמרים הבאים