Cloud SQL (PostgreSQL)¶
Overview¶
Managed PostgreSQL 16 database on Google Cloud SQL with private IP connectivity.
| Attribute | Value |
|---|---|
| Instance | scandora-postgres |
| Region | us-central1 |
| Tier | db-f1-micro (~$8/mo) |
| Storage | 10GB SSD |
| Private IP | 10.200.0.3 |
| Database | dataeng |
| User | joe (cloudsqlsuperuser) |
Architecture¶
graph LR
subgraph GCP ["scandoraproject (GCP)"]
subgraph VPC ["scandora-vpc-us-central1"]
dumbo["dumbo<br/>10.1.0.110"]
proxy["cloud-sql-proxy<br/>localhost:5432"]
end
subgraph PSA ["Private Services Access<br/>10.200.0.0/16"]
cloudsql["Cloud SQL<br/>scandora-postgres<br/>10.200.0.3"]
end
end
dumbo --> proxy
proxy -->|"Private IP"| cloudsql
How It Works¶
- Cloud SQL runs in Google's managed environment with a private IP (10.200.0.3)
- Private Services Access creates a VPC peering connection to Google's network
- Cloud SQL Auth Proxy on dumbo creates a secure tunnel using IAM authentication
- Applications connect to
localhost:5432- the proxy handles encryption and auth
No Public IP
The Cloud SQL instance has no public IP address. All connections go through the VPC peering, keeping database traffic off the public internet.
Connecting¶
From Dumbo¶
# Connect via the local proxy (recommended)
psql -h 127.0.0.1 -U joe -d dataeng
# With password from environment
PGPASSWORD=$(op item get "Cloud SQL - scandora-postgres - joe" --fields password --reveal) \
psql -h 127.0.0.1 -U joe -d dataeng
Connection String¶
Health Check¶
# Proxy readiness
curl http://127.0.0.1:9090/readiness
# Proxy status
systemctl status cloud-sql-proxy
Cloud SQL Auth Proxy¶
The proxy runs as a systemd service on dumbo:
| Attribute | Value |
|---|---|
| Service | cloud-sql-proxy |
| Listen | 127.0.0.1:5432 |
| Health | 127.0.0.1:9090 |
| Auth | IAM (service account) |
Service Management¶
# Status
sudo systemctl status cloud-sql-proxy
# Logs
sudo journalctl -u cloud-sql-proxy -f
# Restart
sudo systemctl restart cloud-sql-proxy
Configuration¶
Managed by Ansible role cloudsql-client:
- Service file:
/etc/systemd/system/cloud-sql-proxy.service - Binary:
/usr/local/bin/cloud-sql-proxy
Credentials¶
1Password Item¶
| Field | Value |
|---|---|
| Title | Cloud SQL - scandora-postgres - joe |
| Vault | Private |
| Tags | scandora.net, gcp, database |
Retrieve Password¶
# From workstation (interactive)
op item get "Cloud SQL - scandora-postgres - joe" --fields password --reveal
# From dumbo (service account)
export OP_SERVICE_ACCOUNT_TOKEN=$(sudo cat /etc/op-service-account.token)
op item get "Cloud SQL - scandora-postgres - joe" --vault Private --fields password --reveal
Reset Password¶
# Generate new password
NEW_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)
# Update in Cloud SQL
gcloud sql users set-password joe \
--instance=scandora-postgres \
--password="$NEW_PASSWORD"
# Update in 1Password manually
echo "New password: $NEW_PASSWORD"
Administration¶
Instance Management¶
# Instance status
gcloud sql instances describe scandora-postgres --format="get(state)"
# Restart instance
gcloud sql instances restart scandora-postgres
# Resize instance (minimal downtime)
gcloud sql instances patch scandora-postgres --tier=db-g1-small
Database Management¶
# List databases
gcloud sql databases list --instance=scandora-postgres
# Create database
gcloud sql databases create mydb --instance=scandora-postgres
# Delete database
gcloud sql databases delete mydb --instance=scandora-postgres
User Management¶
# List users
gcloud sql users list --instance=scandora-postgres
# Create user
gcloud sql users create newuser \
--instance=scandora-postgres \
--password="$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)"
# Delete user
gcloud sql users delete newuser --instance=scandora-postgres
Backups¶
Automated Backups¶
| Setting | Value |
|---|---|
| Schedule | Daily at 4:00 AM UTC (10 PM MDT) |
| Retention | 7 days |
| PITR | Disabled |
Manual Operations¶
# List backups
gcloud sql backups list --instance=scandora-postgres
# Create on-demand backup
gcloud sql backups create --instance=scandora-postgres
# Restore from backup
gcloud sql backups restore BACKUP_ID --restore-instance=scandora-postgres
Restore Behavior
Restoring overwrites the current database. For production data, create a clone first.
Cost¶
| Resource | Monthly Cost |
|---|---|
| db-f1-micro | ~$7 |
| 10GB SSD | ~$2 |
| Network | $0 (private) |
| Backups (7 days) | ~$0.50 |
| Total | ~$10/month |
Resizing¶
| Tier | vCPU | RAM | Monthly |
|---|---|---|---|
| db-f1-micro | Shared | 0.6GB | $8 |
| db-g1-small | Shared | 1.7GB | $25 |
| db-custom-1-3840 | 1 | 3.75GB | $50 |
Infrastructure as Code¶
Terraform¶
# Directory
cd cloud/terraform/environments/production/gce/cloud-sql
# Plan
terraform plan
# Apply (5-10 min for new instance)
terraform apply
# Outputs
terraform output connection_name
terraform output private_ip
Ansible¶
# Deploy proxy to dumbo
cd cloud/ansible
ansible-playbook -i inventory/dumbo.yml playbooks/site.yml --tags cloudsql
Troubleshooting¶
Proxy Won't Start¶
# Check logs
sudo journalctl -u cloud-sql-proxy -n 50
# Common issues:
# - "Config error: instance does not have IP of type PUBLIC"
# Fix: Ensure --private-ip flag is in service file
#
# - "could not find default credentials"
# Fix: Check IAM role on dumbo's service account
Can't Connect to Database¶
# 1. Verify proxy is running
systemctl status cloud-sql-proxy
# 2. Test port is listening
ss -tlnp | grep 5432
# 3. Check health endpoint
curl http://127.0.0.1:9090/readiness
# 4. Check IAM permissions
gcloud projects get-iam-policy scandoraproject \
--flatten="bindings[].members" \
--filter="bindings.role:roles/cloudsql.client"
Slow Performance¶
db-f1-micro is limited. If experiencing slowness:
- Check CPU usage in Cloud Console
- Consider upgrading to db-g1-small (~$25/mo)
- Optimize queries (add indexes, EXPLAIN ANALYZE)
SSL Errors¶
Cloud SQL enforces SSL. The proxy handles this automatically, but direct connections need:
# Download CA certificate
gcloud sql ssl server-ca-certs list --instance=scandora-postgres
# Connect with SSL
psql "host=10.200.0.3 dbname=dataeng user=joe sslmode=verify-ca sslrootcert=server-ca.pem"
Security¶
IAM Roles¶
| Role | Principal | Purpose |
|---|---|---|
roles/cloudsql.client |
Compute default SA | Proxy auth |
Network Security¶
- No public IP - Database unreachable from internet
- VPC peering - Traffic stays in Google network
- SSL required - All connections encrypted
- IAM auth - Proxy authenticates via service account
User Privileges¶
| User | Privileges |
|---|---|
| joe | cloudsqlsuperuser (CREATEDB, CREATEROLE) |
| cloudsqladmin | True superuser (Google internal) |
Superuser Limitation
Cloud SQL doesn't grant true PostgreSQL SUPERUSER to customers. cloudsqlsuperuser is the highest available privilege level.
Related Documentation¶
- Dumbo (GCE) - Host running the proxy
- Ansible Roles - cloudsql-client role
- Terraform Patterns - Cloud SQL module
- Secrets Management - Password handling