This lab deploys a Cloud Run service connected to a Cloud SQL PostgreSQL instance through a private VPC Serverless Connector, without exposing either service to the public internet. The lab demonstrates four Module 4 integration patterns: project-level API enablement, Workload Identity for keyless Cloud SQL authentication, VPC Serverless Connector for private Cloud Run-to-Cloud SQL connectivity, and Secret Manager for database credentials without environment variable secrets. By the end, you have a production-pattern Cloud Run service that never stores database passwords in code or configuration.
The lab builds on the keyless authentication patterns from the Workload Identity Federation lesson, applying them within a single GCP project using GCP’s built-in Workload Identity rather than the cross-cloud WIF pattern. A Cloud Run service’s service account receives the Cloud SQL Client role, enabling the Cloud SQL Auth Proxy to authenticate using IAM credentials rather than a database password. This IAM-based database authentication is the GCP equivalent of the AWS RDS IAM authentication pattern and the Azure managed identity pattern covered in Module 3.
Prerequisites: gcloud CLI installed and authenticated; a GCP project with billing enabled; permissions for Cloud Run, Cloud SQL, VPC, Secret Manager, and Service Account APIs. All resources deploy to asia-south1 (Mumbai) region. Estimated lab duration is 55 minutes including Cloud SQL instance provisioning time, which takes 5 to 10 minutes.
Prerequisites
- gcloud CLI installed and authenticated; verify with `gcloud auth list`.
- Active GCP project with billing account linked; verify with `gcloud billing projects describe YOUR_PROJECT`.
- Required APIs not yet enabled will be enabled in Step 1.
- Docker installed locally for building the container image, or Cloud Build enabled for cloud-based builds.
- Python 3.11 and pip installed for local application development.
Step 1 — Enable APIs and Create Service Account
Enable the six required APIs for this lab and create a dedicated service account for the Cloud Run service. The service account replaces the default Compute Engine service account, which has broad project-level Editor permissions that violate least privilege. The Cloud Run service account will receive only the two permissions it actually needs: reading secrets from Secret Manager and connecting to the Cloud SQL instance via IAM authentication.
import subprocess
PROJECT = 'ipl-scorecard-prod'
REGION = 'asia-south1'
SA_NAME = 'sa-cloudrun-scorecard'
SA_EMAIL = f'{SA_NAME}@{PROJECT}.iam.gserviceaccount.com'
# Enable required APIs.
apis = [
'run.googleapis.com',
'sqladmin.googleapis.com',
'secretmanager.googleapis.com',
'vpcaccess.googleapis.com',
'compute.googleapis.com',
'iam.googleapis.com',
]
subprocess.run([
'gcloud', 'services', 'enable', *apis, '--project', PROJECT
], check=True)
print(f'Enabled {len(apis)} APIs.')
# Create dedicated service account.
subprocess.run([
'gcloud', 'iam', 'service-accounts', 'create', SA_NAME,
'--display-name', 'IPL Cloud Run Scorecard SA',
'--project', PROJECT,
], check=True)
# Grant only the specific roles needed (no Editor or broad roles).
for role in ['roles/cloudsql.client', 'roles/secretmanager.secretAccessor']:
subprocess.run([
'gcloud', 'projects', 'add-iam-policy-binding', PROJECT,
'--member', f'serviceAccount:{SA_EMAIL}',
'--role', role,
], check=True)
print(f'Service account: {SA_EMAIL} with cloudsql.client and secretmanager.secretAccessor.')Step 2 — VPC and Serverless Connector
Create the VPC Serverless Connector that enables Cloud Run services to make outbound connections to resources in the VPC, specifically the Cloud SQL instance’s private IP. The connector reserves a /28 subnet in the VPC specifically for serverless connector instances. Cloud Run services configured with the VPC connector route all outbound traffic through the connector into the VPC, enabling private Cloud SQL connections without the Cloud SQL instance requiring a public IP address.
import subprocess
PROJECT = 'ipl-scorecard-prod'
REGION = 'asia-south1'
# Create VPC (or use existing).
subprocess.run([
'gcloud', 'compute', 'networks', 'create', 'ipl-vpc',
'--subnet-mode', 'auto', '--project', PROJECT,
], check=False) # may already exist
# Enable Private Google Access on the asia-south1 subnet.
subprocess.run([
'gcloud', 'compute', 'networks', 'subnets', 'update', 'ipl-vpc',
'--region', REGION,
'--enable-private-ip-google-access',
'--project', PROJECT,
], check=True)
# Create VPC Serverless Connector.
subprocess.run([
'gcloud', 'compute', 'networks', 'vpc-access', 'connectors', 'create',
'ipl-connector',
'--region', REGION,
'--network', 'ipl-vpc',
'--range', '10.8.0.0/28', # reserved /28 for connector
'--min-instances', '2',
'--max-instances', '10',
'--machine-type', 'e2-micro',
'--project', PROJECT,
], check=True)
print('VPC Serverless Connector created: ipl-connector')Step 3 — Cloud SQL with Private IP
Create the Cloud SQL PostgreSQL instance with a private IP only, no public IP, in the VPC. Private IP Cloud SQL instances are accessible only from resources in the same VPC or connected networks, preventing any internet-facing database exposure. Enable IAM database authentication on the instance, which allows the Cloud Run service’s IAM service account to authenticate as a database user using its GCP IAM credentials rather than a database password.
import subprocess, time
PROJECT = 'ipl-scorecard-prod'
REGION = 'asia-south1'
SQL_INST = 'ipl-postgres'
DB_NAME = 'ipl_scores'
# Create Cloud SQL with private IP only and IAM authentication enabled.
subprocess.run([
'gcloud', 'sql', 'instances', 'create', SQL_INST,
'--database-version', 'POSTGRES_15',
'--tier', 'db-g1-small',
'--region', REGION,
'--network', 'ipl-vpc',
'--no-assign-ip', # private IP only, no public endpoint
'--database-flags', 'cloudsql.iam_authentication=on',
'--project', PROJECT,
], check=True)
print(f'Cloud SQL instance {SQL_INST} created. Waiting for provisioning...')
time.sleep(60) # Cloud SQL provisioning takes 5-10 minutes in production
# Create the database.
subprocess.run([
'gcloud', 'sql', 'databases', 'create', DB_NAME,
'--instance', SQL_INST, '--project', PROJECT,
], check=True)
# Create an IAM database user for the Cloud Run service account.
# This maps the service account to a PostgreSQL IAM user.
SA_EMAIL = f'sa-cloudrun-scorecard@{PROJECT}.iam.gserviceaccount.com'
subprocess.run([
'gcloud', 'sql', 'users', 'create',
# IAM user name is the SA email without the .gserviceaccount.com suffix.
SA_EMAIL.replace('.gserviceaccount.com', ''),
'--instance', SQL_INST,
'--type', 'CLOUD_IAM_SERVICE_ACCOUNT',
'--project', PROJECT,
], check=True)
print(f'IAM database user created for {SA_EMAIL}')Step 4 — Deploy Cloud Run with IAM Database Auth
Write the Cloud Run service application code using the Cloud SQL Python connector with IAM authentication, store the Cloud SQL instance connection name in Secret Manager rather than in the Cloud Run service configuration, and deploy the service with the VPC connector, the dedicated service account, and the Cloud SQL instance connection. The application code uses the Cloud SQL Python connector, which handles the Cloud SQL Auth Proxy functionality in-process without requiring a sidecar container.
import subprocess
PROJECT = 'ipl-scorecard-prod'
REGION = 'asia-south1'
SA_EMAIL = f'sa-cloudrun-scorecard@{PROJECT}.iam.gserviceaccount.com'
SQL_INST = f'{PROJECT}:{REGION}:ipl-postgres'
# Store the Cloud SQL connection name in Secret Manager (no hardcoded config).
subprocess.run([
'gcloud', 'secrets', 'create', 'ipl-cloudsql-connection',
'--data-stdin', '--project', PROJECT,
], input=SQL_INST.encode(), check=True)
print('Cloud SQL connection name stored in Secret Manager.')
# Application code: src/main.py
app_code = '''
from flask import Flask, jsonify
from google.cloud.sql.connector import Connector, IPTypes
from google.cloud import secretmanager
import pg8000.native
import os
app = Flask(__name__)
SM_CLIENT = secretmanager.SecretManagerServiceClient()
def get_db_connection():
"""Create IAM-authenticated Cloud SQL connection via the Python connector."""
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT", "ipl-scorecard-prod")
# Retrieve connection name from Secret Manager (not an env var with the value).
secret_name = f"projects/{project_id}/secrets/ipl-cloudsql-connection/versions/latest"
conn_name = SM_CLIENT.access_secret_version(
name=secret_name
).payload.data.decode()
connector = Connector(ip_type=IPTypes.PRIVATE) # use private IP via VPC connector
# enable_iam_auth=True authenticates using the Cloud Run SA\'s IAM identity.
conn = connector.connect(
conn_name, "pg8000", user="sa-cloudrun-scorecard",
db="ipl_scores", enable_iam_auth=True,
)
return conn
@app.route("/api/scores/<match_id>")
def get_score(match_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT batting_team, runs, wickets, overs FROM innings WHERE match_id = %s",
(match_id,)
)
rows = cursor.fetchall()
return jsonify([dict(zip(["battingTeam","runs","wickets","overs"], row)) for row in rows])
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
'''
with open('src/main.py', 'w') as f: f.write(app_code)
# Deploy Cloud Run service with VPC connector and IAM database auth.
subprocess.run([
'gcloud', 'run', 'deploy', 'ipl-scorecard-api',
'--source', 'src/', # Cloud Build builds the container
'--region', REGION,
'--project', PROJECT,
'--service-account', SA_EMAIL,
'--vpc-connector', 'ipl-connector', # route to Cloud SQL private IP
'--vpc-egress', 'all-traffic',
'--no-allow-unauthenticated', # authenticated via Entra ID or API key
'--set-secrets', 'GOOGLE_CLOUD_PROJECT=ipl-project-id:latest',
], check=True)
print('Cloud Run service deployed with VPC connector and IAM database auth.')Expected Results
- curl https://ipl-scorecard-api-HASH-em.a.run.app/api/scores/IPL2024FINAL with an Authorization header returns a JSON array of innings records from the Cloud SQL database.
- The Cloud SQL instance’s Connections tab shows connections from the Cloud Run connector IP range (10.8.0.0/28) and no connections from public IPs, confirming the private-only network configuration.
- Cloud Audit Logs show the sa-cloudrun-scorecard service account acquiring a Cloud SQL IAM token and a Secret Manager accessSecretVersion event on each cold start, confirming no passwords are stored in the container or environment variables.
- Attempting to connect to the Cloud SQL instance from outside the VPC (using the public IP, which does not exist) returns a connection refused error, confirming the private-only configuration.
- The Cloud Run service’s revision shows VPC Connector: ipl-connector in its configuration details, confirming the connector attachment.
Pro Tip
Use the Cloud SQL Python Connector’s connection pooling by initialising the Connector object and the connection pool outside the request handler function, in the module scope, so they are reused across warm invocations. Creating a new connector and pool on every request adds 100 to 300 milliseconds of connection overhead per request. The pool maintains warm connections to Cloud SQL across invocations, amortising connection setup cost over the Cloud Run instance’s warm lifetime.
Warning: The VPC Serverless Connector’s --vpc-egress all-traffic flag routes all Cloud Run outbound traffic through the VPC, including traffic to external APIs and Google services. This prevents Cloud Run from reaching Google services like Secret Manager directly via their public endpoints. Ensure Private Google Access is enabled on the connector’s subnet so that traffic to Google APIs (secretmanager.googleapis.com, run.googleapis.com) routes through the private Google network rather than failing due to no internet access from the private VPC. Without Private Google Access, Cloud Run services behind the connector cannot reach GCP managed services.