Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions examples/cloudrun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Connecting Cloud Run to Cloud SQL with the Go Connector

This guide provides a comprehensive walkthrough of how to connect a Cloud Run service to a Cloud SQL instance using the Cloud SQL Go Connector. It covers connecting to instances with both public and private IP addresses and demonstrates how to handle database credentials securely.

## Develop a Go Application

The following Go applications demonstrate how to connect to a Cloud SQL instance using the Cloud SQL Go Connector.

### `mysql/main.go` and `postgres/main.go`

These files contain the core application logic for connecting to a Cloud SQL for MySQL or PostgreSQL instance. They provide two separate authentication methods, each exposed at a different route:
- `/`: Password-based authentication
- `/iam`: IAM-based authentication

### `sqlserver/main.go`

This file contains the core application logic for connecting to a Cloud SQL for SQL Server instance. It uses the `cloud-sql-go-connector` to create a database connection pool with password-based authentication at the `/` route.

> [!NOTE]
>
> Cloud SQL for SQL Server does not support IAM database authentication.

> [!NOTE]
> **Lazy Refresh**
>
> The sample code in all three `main.go` files initializes the `Dialer` with `WithLazyRefresh()` option. This is a recommended approach to avoid connection errors and optimize cost by preventing background processes from running when the CPU is throttled.


## Lazy Instantiation

In a Cloud Run service, global variables are initialized when the container instance starts up. The application instance then handles subsequent requests until the container is spun down.

The `Connector` (Dialer) and `sql.DB` objects are defined as global variables (initially set to `nil`) and are lazily instantiated (created only when needed) inside the request handlers using `sync.Once`.

This approach offers several benefits:

1. **Faster Startup:** By deferring initialization until the first request, the Cloud Run service can start listening for requests almost immediately, reducing cold start latency.
2. **Resource Efficiency:** Expensive operations, like establishing background connections or fetching secrets, are only performed when actually required.
3. **Connection Reuse:** Once initialized, the global `Connector` and `sql.DB` instances are reused for all subsequent requests to that container instance. This prevents the overhead of creating new connections for every request and avoids hitting connection limits.

## IAM Authentication Prerequisites

For IAM authentication to work, you must ensure two things:

1. **The Cloud Run service's service account has the `Cloud SQL Client` role.** You can grant this role with the following command:
```bash
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/cloudsql.client"
```
Replace `PROJECT_ID` with your Google Cloud project ID and `SERVICE_ACCOUNT_EMAIL` with the email of the service account your Cloud Run service is using.

2. **The service account is added as a database user to your Cloud SQL instance.** You can do this with the following command:
```bash
gcloud sql users create SERVICE_ACCOUNT_EMAIL \
--instance=INSTANCE_NAME \
--type=cloud_iam_user
```
Replace `SERVICE_ACCOUNT_EMAIL` with the same service account email and `INSTANCE_NAME` with your Cloud SQL instance name.

## Deploy the Application to Cloud Run

Follow these steps to deploy the application to Cloud Run.

### Build and Push the Docker Image

1. **Enable the Artifact Registry API:**

```bash
gcloud services enable artifactregistry.googleapis.com
```

2. **Create an Artifact Registry repository:**

```bash
gcloud artifacts repositories create REPO_NAME \
--repository-format=docker \
--location=REGION
```

3. **Configure Docker to authenticate with Artifact Registry:**

```bash
gcloud auth configure-docker REGION-docker.pkg.dev
```

4. **Build the Docker image (replace `mysql` with `postgres` or `sqlserver` as needed):**

```bash
docker build -t REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME mysql
```

5. **Push the Docker image to Artifact Registry:**

```bash
docker push REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME
```

### Deploy to Cloud Run

Deploy the container image to Cloud Run using the `gcloud run deploy` command.

**Sample Values:**
* `SERVICE_NAME`: `my-cloud-run-service`
* `REGION`: `us-central1`
* `PROJECT_ID`: `my-gcp-project-id`
* `REPO_NAME`: `my-artifact-repo`
* `IMAGE_NAME`: `my-app-image`
* `INSTANCE_CONNECTION_NAME`: `my-gcp-project-id:us-central1:my-instance-name`
* `DB_USER`: `my-db-user` (for password-based authentication)
* `DB_IAM_USER`: `my-service-account@my-gcp-project-id.iam.gserviceaccount.com` (for IAM-based authentication)
* `DB_NAME`: `my-db-name`
* `DB_PASSWORD`: `my-user-pass-name`
* `VPC_NETWORK`: `my-vpc-network`
* `SUBNET_NAME`: `my-vpc-subnet`

**For MySQL and PostgreSQL (Public IP):**

```bash
gcloud run deploy SERVICE_NAME \
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
--set-env-vars=DB_USER=DB_USER,DB_IAM_USER=DB_IAM_USER,DB_NAME=DB_NAME,DB_SECRET_NAME=DB_SECRET_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME \
--region=REGION \
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
```

**For MySQL and PostgreSQL (Private IP):**

```bash
gcloud run deploy SERVICE_NAME \
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
--set-env-vars=DB_USER=DB_USER,DB_IAM_USER=DB_IAM_USER,DB_NAME=DB_NAME,DB_SECRET_NAME=DB_SECRET_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME,IP_TYPE=PRIVATE \
--network=VPC_NETWORK \
--subnet=SUBNET_NAME \
--vpc-egress=private-ranges-only \
--region=REGION \
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
```

**For SQL Server (Public IP):**

```bash
gcloud run deploy SERVICE_NAME \
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
--set-env-vars=DB_USER=DB_USER,DB_NAME=DB_NAME,DB_SECRET_NAME=DB_SECRET_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME \
--region=REGION \
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
```

**For SQL Server (Private IP):**

```bash
gcloud run deploy SERVICE_NAME \
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_name \
--set-env-vars=DB_USER=DB_USER,DB_NAME=DB_NAME,DB_SECRET_NAME=DB_SECRET_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME,IP_TYPE=PRIVATE \
--network=VPC_NETWORK \
--subnet=SUBNET_NAME \
--vpc-egress=private-ranges-only \
--region=REGION \
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
```

> [!NOTE]
> **`For PSC connections`**
>
> To connect to the Cloud SQL instance with PSC connection type, create a PSC endpoint, a DNS zone and DNS record for the instance in the same VPC network as the Cloud Run service and replace the `IP_TYPE` in the deploy command with `PSC`. To configure DNS records, refer to [Connect to an instance using Private Service Connect](https://docs.cloud.google.com/sql/docs/mysql/configure-private-service-connect) guide
34 changes: 34 additions & 0 deletions examples/cloudrun/mysql/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Use the official Golang image to create a build artifact.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.25 as builder

# Create and change to the app directory.
WORKDIR /app

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY . .

# Build the binary.
# CGO_ENABLED=0 builds a statically-linked binary.
RUN CGO_ENABLED=0 GOOS=linux go build -v -o server

# Use the official Alpine image for a lean production container.
# https://hub.docker.com/_/alpine
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM alpine:latest

# Install CA certificates for SSL/TLS connections
RUN apk --no-cache add ca-certificates

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /server

# Run the web service on container startup.
CMD ["/server"]
47 changes: 47 additions & 0 deletions examples/cloudrun/mysql/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module cloud.google.com/go-cloudrun/mysql

go 1.24.0

toolchain go1.24.3

require (
cloud.google.com/go/cloudsqlconn v1.19.0
cloud.google.com/go/secretmanager v1.16.0
github.com/go-sql-driver/mysql v1.9.3
)

require (
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.253.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)
Loading
Loading