Google Cloud SQL with IAM Authentication for GKE
Most of the instructions in this guide are specific to Google Cloud Platform (GCP) infrastructure setup rather than zymtrace itself. We've documented our own experience setting up Cloud SQL with GKE to make it easier for users who want to use this configuration. The zymtrace-specific configuration is minimal - it's primarily about connecting to your PostgreSQL database once it's properly configured in GCP.
The Cloud SQL Proxy acts as a secure tunnel, handling IAM authentication and encryption transparently while providing zymtrace with a standard PostgreSQL connection. This guide demonstrates how to set up Google Cloud SQL PostgreSQL with IAM authentication for use with GKE clusters. This configuration provides secure, password-less database access using Google Cloud's Workload Identity.
This document assumes you're setting up Cloud SQL from scratch. If you already have an existing Cloud SQL instance or GKE cluster, feel free to skip to the relevant sections that apply to your setup.
Overviewβ
What You'll Accomplishβ
By following this guide, you will:
- Create a Cloud SQL PostgreSQL instance with IAM authentication
- Set up Workload Identity between GKE and Cloud SQL
- Configure automatic database creation (optional)
- Deploy zymtrace that securely connects to Cloud SQL without managing passwords
Architectureβ
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β GKE Pod βββββΆβ Cloud SQL Auth βββββΆβ Cloud SQL β
β β β Proxy β β PostgreSQL β
β zymtrace β β β β β
β K8s SA β β IAM Auth β β IAM User β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββ
Workload Identity Binding
Prerequisitesβ
Before you begin, ensure you have:
Requirement | Description |
---|---|
GKE Cluster | Running with Workload Identity enabled (or ability to enable it) |
CLI Tools | gcloud and kubectl installed and authenticated |
Permissions | IAM roles for Cloud SQL, GKE, and service account management |
Required IAM Rolesβ
Your user account needs these roles:
roles/cloudsql.admin
- To create and manage Cloud SQL instancesroles/container.admin
- To manage GKE clusters and node poolsroles/iam.serviceAccountAdmin
- To create and manage service accountsroles/resourcemanager.projectIamAdmin
- To bind IAM policies
Configurationβ
Environment Setupβ
First, set up your environment variables. Replace the example values with your actual project details:
# Project and instance configuration
export PROJECT_ID="your-project-id"
export REGION="us-central1"
export INSTANCE_NAME="zymtrace-postgres"
export DATABASE_VERSION="POSTGRES_17"
# GKE configuration
export CLUSTER_NAME="your-gke-cluster"
export NAMESPACE="your-namespace"
# Service account configuration
export SA_NAME="cloudsql-proxy-sa"
export K8S_SA_NAME="cloudsql-k8s-sa"
Implementation Stepsβ
Step 1: Create Cloud SQL Instanceβ
Create a new PostgreSQL instance with recommended settings for production use:
gcloud sql instances create $INSTANCE_NAME \
--database-version=$DATABASE_VERSION \
--region=$REGION \
--cpu=2 \
--memory=7680MB \
--storage-size=50GB \
--storage-type=SSD \
--storage-auto-increase \
--enable-bin-log \
--maintenance-release-channel=production \
--deletion-protection \
--project=$PROJECT_ID
Set a password for the default postgres user (required for initial setup):
gcloud sql users set-password postgres \
--instance=$INSTANCE_NAME \
--password="$(openssl rand -base64 32)"
The postgres password is only used for initial database setup. zymtrace will use IAM authentication.
Step 2: Configure Service Accountsβ
Create a Google Cloud service account for Cloud SQL access:
# Create the service account
gcloud iam service-accounts create $SA_NAME \
--display-name="zymtrace Cloud SQL Proxy Service Account" \
--description="Enables zymtrace IAM authentication to Cloud SQL from GKE"
# Grant required Cloud SQL permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudsql.instanceUser"
Step 3: Database Setupβ
Create IAM Database Userβ
Create a database user that corresponds to your service account:
gcloud sql users create $SA_NAME@$PROJECT_ID.iam \
--instance=$INSTANCE_NAME \
--type=cloud_iam_service_account
Configure Database Permissionsβ
There are two approaches for setting up database permissions, depending on whether you want to use automatic database creation or manual database creation.
Option 1: Using autoCreateDB Mode (recommended, if possible)β
If you plan to use autoCreateDBs: true
in your Helm configuration, you only need to grant the CREATEDB
permission. The databases will be created automatically by zymtrace on startup.
Connect to your instance:
# Connect as postgres user
gcloud sql connect $INSTANCE_NAME --user=postgres --database=postgres
Run this SQL command:
-- Grant CREATEDB permission (allows zymtrace to create databases automatically)
ALTER USER "[email protected]" CREATEDB;
-- Exit psql
\q
That's it! When you deploy zymtrace with autoCreateDBs: true
, the required databases (zymtrace_identity
and zymtrace_symdb
) will be created automatically with the correct permissions.
Option 2: Using Manual Database Creation Processβ
If you prefer to manually create and manage databases (using autoCreateDBs: false
), follow these steps:
Connect to your instance:
# Connect as postgres user
gcloud sql connect $INSTANCE_NAME --user=postgres --database=postgres
Run these SQL commands:
-- Step 1: Create the databases manually
CREATE DATABASE zymtrace_identity OWNER your-sa-name@your-project.iam;
CREATE DATABASE zymtrace_symdb OWNER your-sa-name@your-project.iam;
-- Step 2: Alter schema
\c zymtrace_identity
ALTER SCHEMA public OWNER TO your-sa-name@your-project.iam;
\c zymtrace_symdb
ALTER SCHEMA public OWNER TO your-sa-name@your-project.iam;
-- Exit psql
\q
Important**: Replace [email protected]
with your actual service account name (e.g., [email protected]
).
Step 4: Configure GKE Workload Identityβ
Verify Node Pool Scopesβ
Check if your node pool has the required OAuth scopes:
gcloud container node-pools describe default-pool \
--cluster=$CLUSTER_NAME \
--region=$REGION \
--format="value(config.oauthScopes)"
The output should include https://www.googleapis.com/auth/cloud-platform
.
If your existing node pool doesn't have the cloud-platform
scope, you cannot update it after creation. Node pool scopes are immutable. You'll need to create a new node pool with the correct scopes.
Create Node Pool with Required Scopes (if needed)β
If your existing node pool lacks the required scopes, create a new node pool specifically for the Cloud SQL proxy:
# Create a new node pool with cloud-platform scope
gcloud container node-pools create cloudsql-pool \
--cluster=$CLUSTER_NAME \
--region=$REGION \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--num-nodes=1 \
--machine-type=e2-medium \
--enable-autorepair \
--enable-autoupgrade
Add the node selector to your custom-values.yaml to ensure the proxy pod is scheduled on this new node.
postgres:
mode: "gcp_cloudsql"
nodeSelector:
cloud.google.com/gke-nodepool: cloudsql-pool
Enable Workload Identityβ
If not already enabled, enable Workload Identity on your cluster:
# Check current status
gcloud container clusters describe $CLUSTER_NAME \
--region=$REGION \
--format="value(workloadIdentityConfig.workloadPool)"
# Enable if needed (takes 10-30 minutes)
gcloud container clusters update $CLUSTER_NAME \
--workload-pool=$PROJECT_ID.svc.id.goog \
--region=$REGION
Set Up Identity Bindingβ
Create Kubernetes resources and bind them to Google Cloud:
# Create namespace
kubectl create namespace $NAMESPACE
# Create Kubernetes service account
kubectl create serviceaccount $K8S_SA_NAME -n $NAMESPACE
# Annotate for Workload Identity
kubectl annotate serviceaccount $K8S_SA_NAME -n $NAMESPACE \
iam.gke.io/gcp-service-account=$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com
# Create the binding
gcloud iam service-accounts add-iam-policy-binding \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:$PROJECT_ID.svc.id.goog[$NAMESPACE/$K8S_SA_NAME]" \
$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com
Verification and Testingβ
Verify Workload Identityβ
Check that Workload Identity is properly configured:
# Verify cluster configuration
gcloud container clusters describe $CLUSTER_NAME \
--region=$REGION \
--format="value(workloadIdentityConfig.workloadPool)"
# Check service account annotation
kubectl get serviceaccount $K8S_SA_NAME -n $NAMESPACE -o yaml
# Verify IAM binding
gcloud iam service-accounts get-iam-policy $SA_NAME@$PROJECT_ID.iam.gserviceaccount.com
Test Database Connectionβ
Create a test pod to verify connectivity:
kubectl run postgres-test --rm -it --image=postgres:17 -n $NAMESPACE -- bash
Inside the test pod, connect to the database:
psql -h postgres-proxy -p 5432 -U "[email protected]" -d zymtrace_identity
Run test queries:
-- Verify connection and user
SELECT current_user, current_database();
Helm Installβ
After completing the verification and testing steps above, you need to configure your Helm deployment with the nodeSelector to ensure the Cloud SQL proxy runs on the node pool with proper scopes.
Configure custom-values.yamlβ
Add the nodeSelector configuration to your custom-values.yaml
:
postgres:
mode: "gcp_cloudsql"
gcp_cloudsql:
instance: "PROJECT_ID:REGION:INSTANCE_NAME" # e.g., "local-bebop-448118-g4:us-central1:zymtrace-cloudsql-psql-1"
user: "SERVICE_ACCOUNT_NAME@PROJECT_ID.iam" # e.g., "[email protected]"
database: "zymtrace"
autoCreateDBs: true # Set to false if you manually created databases
workloadIdentity:
enabled: true
proxy:
# IMPORTANT: Use nodeSelector to ensure proxy runs on node pool with cloud-platform scope
nodeSelector:
cloud.google.com/gke-nodepool: cloudsql-pool
serviceAccount: "cloudsql-k8s-sa" # Your Kubernetes service account name
Deploy and Restartβ
Deploy your Helm chart with the updated configuration:
# Deploy or upgrade with the new configuration
helm upgrade --install backend zymtrace/backend -f custom-values.yaml -n $NAMESPACE
# Restart the proxy deployment to ensure it gets scheduled on the correct node pool
kubectl rollout restart deployment zymtrace-postgres-proxy -n $NAMESPACE
Verify Proxy Deploymentβ
Check that the proxy is running on the correct node pool:
# Check which node the proxy is running on
kubectl get pods -l app=postgres-proxy -n $NAMESPACE -o wide
# Verify the node has the correct node pool label
kubectl get nodes -l cloud.google.com/gke-nodepool=cloudsql-pool
For complete YAML configuration examples, refer to the storage configuration guide.
If you don't configure the nodeSelector properly, you'll see authentication scope errors like:
025/05/26 13:31:09 [xx:us-central1:zymtrace-cloudsql-psql-1] failed to connect to instance: failed to get instance: Refresh error: failed to get instance metadata (connection name = "xx:us-central1:zymtrace-cloudsql-psql-1"): googleapi: Error 403: Request had insufficient authentication scopes.
Details:
[
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"domain": "googleapis.com",
"metadata": {
"method": "google.cloud.sql.v1beta4.SqlConnectService.GetConnectSettings",
"service": "sqladmin.googleapis.com"
},
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
}
]
More details:
Reason: insufficientPermissions, Message: Insufficient Permission
This happens because the proxy is running on a node pool without the cloud-platform
scope. The nodeSelector ensures the proxy runs on the correct node pool.
Troubleshootingβ
Common Issuesβ
Issue | Symptoms | Solution |
---|---|---|
Authentication Scope Error | "Insufficient authentication scopes" | Verify node pool has cloud-platform scope |
Permission Denied | Database connection fails | Check IAM database user and CREATEDB permission |
Workload Identity Issues | Service account authentication fails | Verify annotation and IAM binding |
Proxy Connection Fails | Cannot connect to postgres-proxy | Check proxy pod logs and configuration |
Diagnostic Commandsβ
# Check proxy pod status
kubectl get pods -l app=postgres-proxy -n $NAMESPACE
# View proxy logs
kubectl logs -l app=postgres-proxy -n $NAMESPACE
# Test Workload Identity from pod
kubectl run test-wi --rm -it --image=google/cloud-sdk:slim \
--serviceaccount=$K8S_SA_NAME -n $NAMESPACE -- \
gcloud auth list
Expected Proxy Logsβ
When the Cloud SQL proxy is working correctly, you should see log messages similar to this:
kubectl logs -n your-namespace zymtrace-postgres-proxy-6db65b7446-56nqp
Expected output:
2025/05/26 13:39:58 Authorizing with Application Default Credentials
2025/05/26 13:39:58 [local-bebop-448118-g4:us-central1:zymtrace-cloudsql-psql-1] Listening on [::]:5432
2025/05/26 13:39:58 The proxy has started successfully and is ready for new connections!
2025/05/26 13:40:14 [local-bebop-448118-g4:us-central1:zymtrace-cloudsql-psql-1] Accepted connection from 10.56.4.1:46902
2025/05/26 13:40:14 [local-bebop-448118-g4:us-central1:zymtrace-cloudsql-psql-1] client closed the connection