Table of Contents
There are various ways for a Cloud Run instance to connect to a Cloud SQL instance. Regardless of the solutions, if both resources reside in the same GCP organization, connecting using a Cloud SQL’s private IP with SSL/TLS encryption is highly recommended to prevent network traffic from going out to the internet.
It is possible to have a Cloud SQL instance with both private and public IPs enabled, and there are legitimate reasons for doing so. For example, it allows the developers to connect to the Cloud SQL instance outside the GCP environment. However, utmost care is needed to ensure the private IP is always used when connecting from the Cloud Run instance.
This article evaluates the pros and cons of each solution and provides some recommendations.
Cloud SQL Configuration
For consistency’s sake, SSL/TLS encryption is enabled on the Cloud SQL instance using client certificates.
A client certificate is also created.
Note: Some solutions, such as Cloud SQL Proxy and Cloud SQL Language Connectors, do not need the client certificate. So, these steps can be safely skipped. From the documentation:
With Cloud SQL Auth Proxy and Cloud SQL Connectors, client and server identities are also automatically verified regardless of the SSL mode setting.
Options
Option A: Cloud SQL Language Connectors
This solution uses Cloud SQL Language Connectors, a programming language library maintained by Google.
The following configurations are NOT required:
- SSL certificates in the Cloud SQL instance
- Cloud SQL volume mounting in the Cloud Run instance.
Python App
import os
from google.cloud.sql.connector import Connector, IPTypes
# ... simplified for brevity
connector = Connector(IPTypes.PRIVATE) # use Private IP
connector.connect(
os.getenv('DB_INSTANCE_CONNECTION_NAME'), # project:region:instance
'pymysql',
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASS'),
db=os.getenv('DB_NAME'),
)
Pros
- Very concise configuration in both IaC and application code since there’s no need to manage client certificates.
Cons
- Only supports Python, Java, Go, and Node.js.
Option B: Managed Cloud SQL Proxy in Cloud Run
This solution uses a managed Cloud SQL Proxy that is configurable directly in the Cloud Run instance. Once configured, a Cloud SQL volume mount is created in the Cloud Run instance.
Python App
import os
import pymysql
# ... simplified for brevity
pymysql.connect(
database=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASS'),
unix_socket=f'/cloudsql/{os.getenv("DB_INSTANCE_CONNECTION_NAME")}', # /cloudsql/project:region:instance
)
Pros
- Very concise configuration in both IaC and application code since there’s no need to manage SSL certificates.
Cons
- It doesn’t work if the Cloud SQL instance has both public IP and private IP enabled. There’s no way to specify which IP to use, and Cloud SQL Proxy automatically uses the public IP if it exists.
- Note: If you know how to pull this off, please drop a comment below.
- Additional configurations must be considered when running in production, which introduces many points of failure.
- It may exceed the default Cloud SQL Admin API quota limit.
Option C: Manually Configure Cloud SQL Proxy
This solution manually configures Cloud SQL Proxy via Dockerfile instead of relying on the managed Cloud SQL volume mount from Cloud Run.
Dockerfile
FROM python:3.12-alpine
RUN apk update && apk add curl
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8080
RUN curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.12.0/cloud-sql-proxy.linux.amd64
RUN chmod +x cloud-sql-proxy
CMD ["sh", "-c", "./cloud-sql-proxy --private-ip --port 3306 ${DB_INSTANCE_CONNECTION_NAME} & sleep 5 && python app.py"]
Python App
import os
import pymysql
# ... simplified for brevity
pymysql.connect(
host=os.getenv('DB_HOST'), # 127.0.0.1
database=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASS'),
)
Pros
- Very concise configuration in both IaC and application code since there’s no need to manage client certificates.
- Unlike the managed Cloud SQL Proxy volume mount solution, this solution will work even if the Cloud SQL instance has both private and public IPs.
Cons
- Need to manage the Cloud SQL Proxy installation and configuration in the Dockerfile.
- All the cons described in the managed Cloud SQL Proxy solution above still apply here.
Option D: Use Cloud SQL’s Private IP and Client Certificate
Instead of relying on external tools/libraries, the application code connects directly to the Cloud SQL instance using the private IP where the client certificate is also provided.
Python App
import os
import pymysql
# ... simplified for brevity
pymysql.connect(
host=os.getenv('DB_HOST'), # Cloud SQL's Private IP
database=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASS'),
ssl={
'ca': os.getenv('DB_SSL_SERVER_CA'), # /app/certs/ca/server-ca.pem
'cert': os.getenv('DB_SSL_CLIENT_CERT'), # /app/certs/cert/client-cert.pem
'key': os.getenv('DB_SSL_CLIENT_KEY'), # /app/certs/key/client-key.pem
'check_hostname': False,
}
)
Note: The hostname checking must be disabled because the database host (Cloud SQL’s Private IP) will never
match the common name specified in the certificate. If the hostname checking is not disabled, it will produce the following error:
Error connecting to database: (2003, “Can’t connect to MySQL server on ’10.x.x.x’ ([SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: IP address mismatch, certificate is not valid for ’10.x.x.x’. (_ssl.c:1000))”)
Pros
- No additional wrappers like Cloud SQL Proxy or Cloud SQL Language Connectors are required.
Cons
- Additional configurations and complexity on the IaC and application code are needed to manage and ingest the client certificate securely.
Recommendations
Best Solution – Option A
Use Cloud SQL Language Connectors if your app uses one of the supported programming languages (Python, Java, Go, or Node.js).
It eliminates many unnecessary configurations on the IaC code and application code. In other words, there’s no need to do the following:
- Create a client certificate for Cloud SQL.
- Store server certificate, client certificate, and client key in Secret Manager securely.
- Mount files containing the server certificate, client certificate, and client key as volumes in the Cloud Run instance.
- Pass certificate information to the application code via environment variables.
None of the above is needed.
Less codebase = Less chance of errors.
Distance Next Best Solution – Option D
Connect directly using the Cloud SQL instance’s Private IP and client certificate.
It eliminates the need to rely on additional cloud configuration or external library (i.e., fewer points of failure).
However, it introduces additional configurations in IaC and application code to maintain and ingest the certificate described above.
Least Favorite Solution – Option B & C
While both Cloud SQL Proxy solutions (volume mount in the Cloud Run instance or installing it directly via Dockerfile) don’t require a client certificate, they come with additional considerations and complexities when running in production.
Furthermore, the volume mount solution won’t work if the Cloud SQL instance has both private and public IPs.
Leave a Reply