mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
Merge b2930f7269 into 82ca10cc2d
This commit is contained in:
commit
90517cf75b
10 changed files with 81 additions and 15 deletions
|
|
@ -8,8 +8,8 @@ Deploys the componentized LiteLLM proxy on GCP:
|
|||
|
||||
- **VPC** + Private Services Access range + a Serverless VPC Access connector
|
||||
so Cloud Run can reach private IPs
|
||||
- **Cloud SQL for PostgreSQL** — primary instance + cross-zone read replica,
|
||||
password auth via Secret Manager
|
||||
- **Cloud SQL for PostgreSQL** — primary instance + optional cross-zone read
|
||||
replica (`create_read_replica`, default true). Password auth via Secret Manager
|
||||
- **Memorystore (Redis)** for caching + rate limiting, private IP only
|
||||
- **GCS bucket** — private, versioned, uniform IAM; exposed as `GCS_BUCKET_NAME`
|
||||
- **Secret Manager** entries for `LITELLM_MASTER_KEY` and `DATABASE_PASSWORD`
|
||||
|
|
@ -96,9 +96,9 @@ spec. This stack therefore uses **password authentication**:
|
|||
- Each Cloud Run service receives the password as `DATABASE_PASSWORD` via
|
||||
`value_source.secret_key_ref`.
|
||||
- The container's entrypoint shim assembles `DATABASE_URL` (and
|
||||
`DATABASE_URL_READ_REPLICA`) from `DATABASE_HOST` / `DATABASE_PASSWORD`
|
||||
before exec'ing uvicorn — so the password never appears in the service
|
||||
spec or in logs.
|
||||
`DATABASE_URL_READ_REPLICA` when a replica is created) from
|
||||
`DATABASE_HOST` / `DATABASE_PASSWORD` before exec'ing uvicorn — so the
|
||||
password never appears in the service spec or in logs.
|
||||
|
||||
If you need GCP-native IAM auth later, add `cloud-sql-proxy` as a sidecar
|
||||
container under `template.template.containers` (Cloud Run v2 supports
|
||||
|
|
@ -559,6 +559,9 @@ masterKey:
|
|||
secretName: <kubernetes-secret-with-master-key>
|
||||
```
|
||||
|
||||
With `create_read_replica = false`, leave out the `reader` block, since
|
||||
`cloudsql_reader_ip` is null
|
||||
|
||||
Create the database Secret with keys `username` (the `db_username` output)
|
||||
and `password` (read it with `gcloud secrets versions access latest
|
||||
--secret=<db_password_secret_id>`), and the master key Secret from
|
||||
|
|
@ -587,6 +590,12 @@ Two opt-in tripwires guard against accidental data loss on
|
|||
Flip `cloudsql_deletion_protection` to `false` or `gcs_force_destroy` to
|
||||
`true` only for ephemeral / CI stacks where you accept losing the data.
|
||||
|
||||
Setting `create_read_replica = false` on an existing stack destroys the
|
||||
replica, so that apply also needs `cloudsql_deletion_protection = false`;
|
||||
set it back to `true` afterwards. The replica can be gone before the Cloud
|
||||
Run revisions that drop `DATABASE_URL_READ_REPLICA` are serving, so reads
|
||||
routed to the replica can fail during that apply
|
||||
|
||||
## Redis encryption
|
||||
|
||||
By default, Memorystore runs with
|
||||
|
|
|
|||
|
|
@ -14,8 +14,12 @@ locals {
|
|||
{ name = "DATABASE_PORT", value = "5432" },
|
||||
{ name = "DATABASE_USER", value = var.db_username },
|
||||
{ name = "DATABASE_NAME", value = var.db_name },
|
||||
{ name = "DATABASE_HOST_READ_REPLICA", value = google_sql_database_instance.reader.private_ip_address },
|
||||
],
|
||||
var.create_read_replica ? [
|
||||
{ name = "DATABASE_HOST_READ_REPLICA", value = google_sql_database_instance.reader[0].private_ip_address },
|
||||
{ name = "DATABASE_PORT_READ_REPLICA", value = "5432" },
|
||||
] : [],
|
||||
[
|
||||
{ name = "REDIS_HOST", value = google_redis_instance.this.host },
|
||||
{ name = "REDIS_PORT", value = tostring(google_redis_instance.this.port) },
|
||||
],
|
||||
|
|
@ -133,10 +137,14 @@ locals {
|
|||
"python -c \"import os, base64, pathlib; pathlib.Path(os.environ['REDIS_SSL_CA_CERTS']).write_bytes(base64.b64decode(os.environ['REDIS_CA_PEM_B64']))\""
|
||||
] : []
|
||||
|
||||
database_url_fragment = [
|
||||
"export DATABASE_URL=\"postgresql://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST}:$${DATABASE_PORT}/$${DATABASE_NAME}\"",
|
||||
"export DATABASE_URL_READ_REPLICA=\"postgresql://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST_READ_REPLICA}:$${DATABASE_PORT_READ_REPLICA}/$${DATABASE_NAME}\"",
|
||||
]
|
||||
database_url_fragment = concat(
|
||||
[
|
||||
"export DATABASE_URL=\"postgresql://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST}:$${DATABASE_PORT}/$${DATABASE_NAME}\"",
|
||||
],
|
||||
var.create_read_replica ? [
|
||||
"export DATABASE_URL_READ_REPLICA=\"postgresql://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST_READ_REPLICA}:$${DATABASE_PORT_READ_REPLICA}/$${DATABASE_NAME}\"",
|
||||
] : [],
|
||||
)
|
||||
|
||||
gateway_pool_env = var.gateway_connection_pool_enabled ? [
|
||||
{ name = "LITELLM_PGBOUNCER_ENABLED", value = "true" },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Cloud SQL for PostgreSQL — one primary + one read replica.
|
||||
# Cloud SQL for PostgreSQL — one primary + an optional read replica.
|
||||
#
|
||||
# Note on auth: LiteLLM's IAM-auth helper (rds_iam_token.py) mints AWS RDS
|
||||
# tokens via boto3 and doesn't speak GCP IAM. Cloud SQL IAM auth from Cloud
|
||||
|
|
@ -64,6 +64,7 @@ resource "google_sql_database_instance" "writer" {
|
|||
}
|
||||
|
||||
resource "google_sql_database_instance" "reader" {
|
||||
count = var.create_read_replica ? 1 : 0
|
||||
name = "${local.name}-reader"
|
||||
region = var.region
|
||||
database_version = var.db_version
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ module "litellm" {
|
|||
env = var.env
|
||||
|
||||
create_runtime = var.create_runtime
|
||||
create_read_replica = var.create_read_replica
|
||||
network_id = var.network_id
|
||||
create_psa_connection = var.create_psa_connection
|
||||
redis_transit_encryption = var.redis_transit_encryption
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ output "cloudsql_writer_ip" {
|
|||
}
|
||||
|
||||
output "cloudsql_reader_ip" {
|
||||
description = "Private IP of the Cloud SQL read replica."
|
||||
description = "Private IP of the Cloud SQL read replica, or null if none."
|
||||
value = module.litellm.cloudsql_reader_ip
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ env = "stage"
|
|||
# create_runtime = false, network_id to the full host-project network ID, and
|
||||
# create_psa_connection = false after configuring PSA on that network.
|
||||
# create_runtime = true
|
||||
# create_read_replica = true
|
||||
# network_id = ""
|
||||
# create_psa_connection = true
|
||||
# redis_transit_encryption = true
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ variable "create_runtime" {
|
|||
default = true
|
||||
}
|
||||
|
||||
variable "create_read_replica" {
|
||||
description = "Create the Cloud SQL read replica. False = writer only."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "network_id" {
|
||||
description = "Existing VPC network resource ID. Empty creates a VPC."
|
||||
type = string
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ output "cloudsql_writer_ip" {
|
|||
}
|
||||
|
||||
output "cloudsql_reader_ip" {
|
||||
description = "Private IP of the Cloud SQL read replica."
|
||||
value = google_sql_database_instance.reader.private_ip_address
|
||||
description = "Private IP of the Cloud SQL read replica, or null when create_read_replica is false."
|
||||
value = one(google_sql_database_instance.reader[*].private_ip_address)
|
||||
}
|
||||
|
||||
output "redis_endpoint" {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ run "default_creates_everything" {
|
|||
length(google_cloud_run_v2_job.migrations) == 1,
|
||||
length(google_compute_global_address.lb) == 1,
|
||||
length(terraform_data.migration) == 1,
|
||||
length(google_sql_database_instance.reader) == 1,
|
||||
])
|
||||
error_message = "The default mode must create networking, runtime services, the load balancer, and migrations."
|
||||
}
|
||||
|
|
@ -96,7 +97,7 @@ run "deps_only_creates_no_runtime" {
|
|||
assert {
|
||||
condition = alltrue([
|
||||
google_sql_database_instance.writer.name == "tenant-litellm-test",
|
||||
google_sql_database_instance.reader.name == "tenant-litellm-test-reader",
|
||||
google_sql_database_instance.reader[0].name == "tenant-litellm-test-reader",
|
||||
google_redis_instance.this.name == "tenant-litellm-test",
|
||||
google_storage_bucket.this.force_destroy == false,
|
||||
google_secret_manager_secret.master_key.secret_id == "tenant-litellm-test-master-key",
|
||||
|
|
@ -173,3 +174,36 @@ run "redis_plaintext_drops_tls_env" {
|
|||
error_message = "Plaintext Redis mode must not decode a Redis CA at startup."
|
||||
}
|
||||
}
|
||||
|
||||
run "skip_read_replica" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
create_read_replica = false
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(google_sql_database_instance.reader) == 0
|
||||
error_message = "create_read_replica = false must omit the replica instance."
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = output.cloudsql_reader_ip == null
|
||||
error_message = "cloudsql_reader_ip must be null without a replica."
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length([for env in local.shared_env_kv : env if endswith(env.name, "_READ_REPLICA")]) == 0
|
||||
error_message = "Writer-only mode must omit both DATABASE_*_READ_REPLICA entries."
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length([for line in local.database_url_fragment : line if strcontains(line, "DATABASE_URL_READ_REPLICA")]) == 0
|
||||
error_message = "Writer-only mode must not export DATABASE_URL_READ_REPLICA."
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = anytrue([for line in local.database_url_fragment : startswith(line, "export DATABASE_URL=")])
|
||||
error_message = "Writer-only mode must still export DATABASE_URL."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,6 +330,12 @@ variable "ui_max_instance_request_concurrency" {
|
|||
|
||||
# ---------- Cloud SQL ----------
|
||||
|
||||
variable "create_read_replica" {
|
||||
description = "Create a Cloud SQL read replica and set DATABASE_URL_READ_REPLICA. Default true. Set false to run writer-only."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "db_tier" {
|
||||
description = "Cloud SQL tier (machine type) for the writer instance."
|
||||
type = string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue