mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
refactor: convert AWS and GCP Terraform stacks into reusable modules with examples/default entry point
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
- Remove `provider` blocks from both AWS and GCP stack roots so the modules can be consumed with `count`, `for_each`, `depends_on`, assumed-role or aliased providers — patterns that are forbidden when a module owns its own provider configuration - Add `examples/default/` thin-root wrappers for both stacks that wire the provider (AWS) / providers (google + google-beta) and call the module with a curated variable surface, preserving the one-command deploy experience - Move `terraform.tfvars.example` files into `examples/default/` alongside the new roots; update example comments to reflect the curated variable surface - Thread `local.tags` (containing `litellm:stack`, `managed-by`, and `var.tags`) explicitly onto every taggable AWS resource since the module no longer controls the provider's `default_tags`; GCP resource labels already flow through the module's `labels` input - Add `examples/default/variables.tf` and `outputs.tf` for both stacks, exposing the most-used knobs and re-exporting all module outputs - Commit provider lock files for both examples so `terraform init` is reproducible without a network fetch - Update top-level and per-stack READMEs to document the module-first design, the `for_each` multi-tenant pattern, and the `examples/default/` quick-start path
This commit is contained in:
parent
3d5a9ede05
commit
b8d6ff7eb3
33 changed files with 880 additions and 72 deletions
|
|
@ -1,18 +1,34 @@
|
|||
# LiteLLM Terraform stacks
|
||||
|
||||
Two self-contained Terraform root modules that deploy the **componentized**
|
||||
LiteLLM proxy — the gateway, backend, and UI as three independent containers
|
||||
(see `helm/litellm/` for the canonical chart with the same split).
|
||||
Two self-contained, reusable Terraform **modules** that deploy the
|
||||
**componentized** LiteLLM proxy — the gateway, backend, and UI as three
|
||||
independent containers (see `helm/litellm/` for the canonical chart with the
|
||||
same split).
|
||||
|
||||
Each module declares **no `provider` block of its own**, so it can be called
|
||||
with `count` / `for_each` / `depends_on` and the caller controls region,
|
||||
assume-role / impersonation, aliases, and `default_tags`. A ready-to-run root
|
||||
that wires the provider lives at `<stack>/examples/default/` — that's the
|
||||
one-command deploy path. To embed a stack in your own config, call the module
|
||||
by source:
|
||||
|
||||
```hcl
|
||||
module "litellm" {
|
||||
source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=<tag>"
|
||||
# ... inputs ...
|
||||
}
|
||||
```
|
||||
|
||||
| Stack | Compute | Database (writer + reader) | Cache | Object store | Public entrypoint |
|
||||
| ------ | ----------- | ---------------------------------- | ----------- | ------------ | ------------------ |
|
||||
| `aws/` | ECS Fargate | Aurora Postgres (IAM auth) | ElastiCache | S3 | Application LB |
|
||||
| `gcp/` | Cloud Run | Cloud SQL Postgres (password auth) | Memorystore | GCS | External HTTPS LB |
|
||||
|
||||
Each stack creates its own VPC and managed data stores — drop in a tfvars
|
||||
file and run `terraform apply`. Both stacks support a typed `proxy_config`
|
||||
input (mirrors `helm/litellm`'s `gateway.config.proxy_config`) and per-component
|
||||
extra env vars / secret-manager refs.
|
||||
Each stack creates its own VPC and managed data stores — from
|
||||
`<stack>/examples/default/`, drop in a tfvars file and run `terraform apply`.
|
||||
Both stacks support a typed `proxy_config` input (mirrors `helm/litellm`'s
|
||||
`gateway.config.proxy_config`) and per-component extra env vars /
|
||||
secret-manager refs.
|
||||
|
||||
## Components
|
||||
|
||||
|
|
|
|||
|
|
@ -132,10 +132,11 @@ pair differs:
|
|||
| `acme` | `prod` | `acme-litellm-prod-master-key` |
|
||||
| `globex` | `dev` | `globex-litellm-dev-license` |
|
||||
|
||||
For a per-tenant instance, the only inputs that change are the tenant
|
||||
slug, env, and the two pre-issued secrets:
|
||||
For a per-tenant instance via the example root, the only inputs that
|
||||
change are the tenant slug, env, and the two pre-issued secrets:
|
||||
|
||||
```bash
|
||||
cd terraform/litellm/aws/examples/default
|
||||
export TF_VAR_litellm_master_key="sk-..." # the tenant's master key
|
||||
export TF_VAR_litellm_license="lic-..." # their LITELLM_LICENSE
|
||||
|
||||
|
|
@ -146,6 +147,22 @@ terraform apply \
|
|||
-var "env=stage"
|
||||
```
|
||||
|
||||
To run *many* tenants from a single config, call the module with
|
||||
`for_each` instead of one root per tenant (see "Using as a module"):
|
||||
|
||||
```hcl
|
||||
module "litellm" {
|
||||
for_each = toset(["acme", "globex"])
|
||||
source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=<tag>"
|
||||
tenant = each.key
|
||||
env = "prod"
|
||||
region = "us-west-2"
|
||||
azs = ["us-west-2a", "us-west-2b"]
|
||||
}
|
||||
```
|
||||
(This `for_each` form is only possible because the module declares no
|
||||
provider block — the original root-with-provider layout forbade it.)
|
||||
|
||||
Both `litellm_master_key` and `litellm_license` are optional:
|
||||
- Omit `litellm_master_key` → the stack auto-generates a random `sk-…`
|
||||
value (trial/dev path).
|
||||
|
|
@ -159,14 +176,21 @@ example files.
|
|||
## Quick start
|
||||
|
||||
```bash
|
||||
cd terraform/litellm/aws
|
||||
cd terraform/litellm/aws/examples/default
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
# Edit: region, tenant, env, azs, *_image, proxy_config, gateway_extra_secrets.
|
||||
# Edit: region, tenant, env, azs, proxy_config, gateway_extra_secrets.
|
||||
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
`examples/default/` is a thin root that configures the `aws` provider and
|
||||
calls the module (`../../`). It exposes a curated variable surface; for
|
||||
advanced knobs (per-component CPU/memory/workers, autoscaling, RDS/Redis
|
||||
sizing, per-component image pins) set them on the `module "litellm"` block
|
||||
in `examples/default/main.tf`, or call the module from your own config —
|
||||
see "Using as a module" below.
|
||||
|
||||
That single apply provisions everything, runs the DB user bootstrap, runs the
|
||||
schema migration, and only then starts the gateway/backend services. When it
|
||||
returns, the stack is serving traffic.
|
||||
|
|
@ -179,6 +203,34 @@ aws secretsmanager get-secret-value \
|
|||
--query SecretString --output text
|
||||
```
|
||||
|
||||
## Using as a module
|
||||
|
||||
The directory itself is a module with **no `provider` block** — the caller
|
||||
owns provider config. That means you can call it directly with `for_each`
|
||||
(many tenants from one config), `count` (conditional stacks), `depends_on`,
|
||||
an assume-role / aliased provider, etc.:
|
||||
|
||||
```hcl
|
||||
provider "aws" {
|
||||
region = "us-west-2"
|
||||
assume_role { role_arn = "arn:aws:iam::111122223333:role/deployer" }
|
||||
}
|
||||
|
||||
module "litellm" {
|
||||
source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=<tag>"
|
||||
|
||||
region = "us-west-2"
|
||||
tenant = "acme"
|
||||
env = "prod"
|
||||
azs = ["us-west-2a", "us-west-2b"]
|
||||
# ...any of the inputs in variables.tf...
|
||||
}
|
||||
```
|
||||
|
||||
Tags: the module threads its own `litellm:stack` / `managed-by` / `var.tags`
|
||||
onto every taggable resource. Any `default_tags` on your provider merge on
|
||||
top — set org-wide tags there, per-deployment tags via the `tags` input.
|
||||
|
||||
## Image pulls
|
||||
|
||||
The defaults pull from `ghcr.io/berriai/litellm-<component>:v1.86.0-dev`,
|
||||
|
|
@ -238,8 +290,8 @@ losing the contents.
|
|||
|
||||
| File | What's in it |
|
||||
| ----------------- | --------------------------------------------------------------------- |
|
||||
| `versions.tf` | Terraform + provider version constraints |
|
||||
| `providers.tf` | AWS provider (region + default tags) |
|
||||
| `versions.tf` | Terraform + `required_providers` constraints (module declares no provider config) |
|
||||
| `examples/default/` | Thin root: `aws` provider + `default_tags` + a call to the module. The one-command deploy path. |
|
||||
| `variables.tf` | All input variables |
|
||||
| `locals.tf` | Path-prefix lists for ALB routing (mirror of `helm/.../ingress.yaml`) |
|
||||
| `network.tf` | VPC, subnets, IGW, NAT, route tables, security groups |
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ resource "aws_lb" "this" {
|
|||
subnets = aws_subnet.public[*].id
|
||||
|
||||
idle_timeout = 120
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
locals {
|
||||
|
|
@ -35,6 +37,8 @@ resource "aws_lb_target_group" "gateway" {
|
|||
}
|
||||
|
||||
deregistration_delay = 30
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_lb_target_group" "backend" {
|
||||
|
|
@ -54,6 +58,8 @@ resource "aws_lb_target_group" "backend" {
|
|||
}
|
||||
|
||||
deregistration_delay = 30
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_lb_target_group" "ui" {
|
||||
|
|
@ -73,6 +79,8 @@ resource "aws_lb_target_group" "ui" {
|
|||
}
|
||||
|
||||
deregistration_delay = 30
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# HTTP listener. When TLS is enabled this only serves a permanent
|
||||
|
|
@ -106,6 +114,8 @@ resource "aws_lb_listener" "http" {
|
|||
error_message = "ALB has no HTTPS listener. Either set `acm_certificate_arn` to enable TLS, or set `allow_plaintext_alb = true` to opt into HTTP-only (trial / dev only)."
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# HTTPS listener. Only created when an ACM cert ARN is supplied — terminates
|
||||
|
|
@ -122,6 +132,8 @@ resource "aws_lb_listener" "https" {
|
|||
type = "forward"
|
||||
target_group_arn = aws_lb_target_group.backend.arn
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# UI exact paths (/, /favicon.ico, /ui) — priority 10.
|
||||
|
|
@ -139,6 +151,8 @@ resource "aws_lb_listener_rule" "ui_exact" {
|
|||
values = local.ui_exact_paths
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# UI prefix paths (/_next/*, /litellm-asset-prefix/*, /assets/*, /ui/*) — priority 20.
|
||||
|
|
@ -156,6 +170,8 @@ resource "aws_lb_listener_rule" "ui_prefix" {
|
|||
values = local.ui_path_prefixes
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# Gateway prefix rules — one per chunk-of-5 because ALB caps a path-pattern
|
||||
|
|
@ -176,4 +192,6 @@ resource "aws_lb_listener_rule" "gateway" {
|
|||
values = each.value
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ resource "aws_iam_policy" "bootstrap_secrets" {
|
|||
Resource = [aws_secretsmanager_secret.db_master_password.arn]
|
||||
}]
|
||||
})
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "task_execution_bootstrap_secrets" {
|
||||
|
|
@ -43,6 +45,8 @@ resource "aws_iam_role_policy_attachment" "task_execution_bootstrap_secrets" {
|
|||
resource "aws_cloudwatch_log_group" "bootstrap_db" {
|
||||
name = "/ecs/${local.name}/bootstrap-db"
|
||||
retention_in_days = var.log_retention_days
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
locals {
|
||||
|
|
@ -101,6 +105,8 @@ resource "aws_ecs_task_definition" "bootstrap_db" {
|
|||
}
|
||||
}
|
||||
}])
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# ---------- Bootstrap trigger ----------
|
||||
|
|
|
|||
|
|
@ -5,26 +5,36 @@ resource "aws_ecs_cluster" "this" {
|
|||
name = "containerInsights"
|
||||
value = "enabled"
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_group" "gateway" {
|
||||
name = "/ecs/${local.name}/gateway"
|
||||
retention_in_days = var.log_retention_days
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_group" "backend" {
|
||||
name = "/ecs/${local.name}/backend"
|
||||
retention_in_days = var.log_retention_days
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_group" "ui" {
|
||||
name = "/ecs/${local.name}/ui"
|
||||
retention_in_days = var.log_retention_days
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_group" "migrations" {
|
||||
name = "/ecs/${local.name}/migrations"
|
||||
retention_in_days = var.log_retention_days
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# Shared env block fed to gateway, backend, and the migration task. Mirrors
|
||||
|
|
@ -169,6 +179,8 @@ resource "aws_ecs_task_definition" "gateway" {
|
|||
local.gateway_proxy_overrides,
|
||||
)
|
||||
])
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_ecs_service" "gateway" {
|
||||
|
|
@ -206,6 +218,8 @@ resource "aws_ecs_service" "gateway" {
|
|||
aws_lb_listener.https,
|
||||
terraform_data.migration,
|
||||
]
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# ---------- Backend ----------
|
||||
|
|
@ -246,6 +260,8 @@ resource "aws_ecs_task_definition" "backend" {
|
|||
local.backend_proxy_overrides,
|
||||
)
|
||||
])
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_ecs_service" "backend" {
|
||||
|
|
@ -279,6 +295,8 @@ resource "aws_ecs_service" "backend" {
|
|||
aws_lb_listener.https,
|
||||
terraform_data.migration,
|
||||
]
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# ---------- UI ----------
|
||||
|
|
@ -312,6 +330,8 @@ resource "aws_ecs_task_definition" "ui" {
|
|||
}
|
||||
}
|
||||
])
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_ecs_service" "ui" {
|
||||
|
|
@ -344,4 +364,6 @@ resource "aws_ecs_service" "ui" {
|
|||
aws_lb_listener.http,
|
||||
aws_lb_listener.https,
|
||||
]
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
46
terraform/litellm/aws/examples/default/.terraform.lock.hcl
generated
Normal file
46
terraform/litellm/aws/examples/default/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.100.0"
|
||||
constraints = "~> 5.60"
|
||||
hashes = [
|
||||
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
|
||||
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
|
||||
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
|
||||
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
|
||||
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
|
||||
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
|
||||
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
|
||||
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
|
||||
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
|
||||
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
|
||||
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
|
||||
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
|
||||
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
|
||||
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.9.0"
|
||||
constraints = "~> 3.6"
|
||||
hashes = [
|
||||
"h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=",
|
||||
"zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1",
|
||||
"zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",
|
||||
"zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f",
|
||||
"zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0",
|
||||
"zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61",
|
||||
"zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc",
|
||||
"zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e",
|
||||
"zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef",
|
||||
"zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b",
|
||||
"zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257",
|
||||
"zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04",
|
||||
]
|
||||
}
|
||||
40
terraform/litellm/aws/examples/default/main.tf
Normal file
40
terraform/litellm/aws/examples/default/main.tf
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# One-command deploy of the LiteLLM AWS stack.
|
||||
#
|
||||
# cd terraform/litellm/aws/examples/default
|
||||
# cp terraform.tfvars.example terraform.tfvars # edit it
|
||||
# terraform init
|
||||
# terraform apply
|
||||
#
|
||||
# This root just wires the provider (see providers.tf) to the module. The
|
||||
# module itself (../../) declares no provider, so it can also be consumed
|
||||
# from your own config with count/for_each/aliased or assume-role providers:
|
||||
#
|
||||
# module "litellm" {
|
||||
# source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=<tag>"
|
||||
# ...
|
||||
# }
|
||||
#
|
||||
# Knobs not surfaced as variables here (per-component sizing, autoscaling,
|
||||
# RDS/Redis tuning) can be set directly on this block — see ../../variables.tf.
|
||||
module "litellm" {
|
||||
source = "../../"
|
||||
|
||||
region = var.region
|
||||
tenant = var.tenant
|
||||
env = var.env
|
||||
azs = var.azs
|
||||
|
||||
litellm_master_key = var.litellm_master_key
|
||||
litellm_license = var.litellm_license
|
||||
ui_password = var.ui_password
|
||||
|
||||
acm_certificate_arn = var.acm_certificate_arn
|
||||
allow_plaintext_alb = var.allow_plaintext_alb
|
||||
s3_force_destroy = var.s3_force_destroy
|
||||
|
||||
proxy_config = var.proxy_config
|
||||
gateway_extra_env = var.gateway_extra_env
|
||||
backend_extra_env = var.backend_extra_env
|
||||
gateway_extra_secrets = var.gateway_extra_secrets
|
||||
backend_extra_secrets = var.backend_extra_secrets
|
||||
}
|
||||
54
terraform/litellm/aws/examples/default/outputs.tf
Normal file
54
terraform/litellm/aws/examples/default/outputs.tf
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
output "alb_dns_name" {
|
||||
description = "Public DNS name of the LiteLLM ALB."
|
||||
value = module.litellm.alb_dns_name
|
||||
}
|
||||
|
||||
output "alb_url" {
|
||||
description = "Proxy URL. Dashboard at /, API at /v1/*."
|
||||
value = module.litellm.alb_url
|
||||
}
|
||||
|
||||
output "ecs_cluster" {
|
||||
description = "ECS cluster name."
|
||||
value = module.litellm.ecs_cluster
|
||||
}
|
||||
|
||||
output "aurora_writer_endpoint" {
|
||||
description = "Aurora writer endpoint."
|
||||
value = module.litellm.aurora_writer_endpoint
|
||||
}
|
||||
|
||||
output "aurora_reader_endpoint" {
|
||||
description = "Aurora reader endpoint."
|
||||
value = module.litellm.aurora_reader_endpoint
|
||||
}
|
||||
|
||||
output "redis_endpoint" {
|
||||
description = "ElastiCache Redis primary endpoint (TLS)."
|
||||
value = module.litellm.redis_endpoint
|
||||
}
|
||||
|
||||
output "s3_bucket" {
|
||||
description = "S3 bucket name."
|
||||
value = module.litellm.s3_bucket
|
||||
}
|
||||
|
||||
output "master_key_secret_arn" {
|
||||
description = "Secrets Manager ARN holding LITELLM_MASTER_KEY."
|
||||
value = module.litellm.master_key_secret_arn
|
||||
}
|
||||
|
||||
output "db_master_password_secret_arn" {
|
||||
description = "Secrets Manager ARN holding the Aurora master credentials (bootstrap-only)."
|
||||
value = module.litellm.db_master_password_secret_arn
|
||||
}
|
||||
|
||||
output "db_bootstrap_sql" {
|
||||
description = "Run once as the master DB user to create the IAM-authed app user."
|
||||
value = module.litellm.db_bootstrap_sql
|
||||
}
|
||||
|
||||
output "migration_run_command" {
|
||||
description = "Break-glass command to re-run the one-off prisma migration task."
|
||||
value = module.litellm.migration_run_command
|
||||
}
|
||||
18
terraform/litellm/aws/examples/default/providers.tf
Normal file
18
terraform/litellm/aws/examples/default/providers.tf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# The provider is configured HERE, in the root, not in the module. That is
|
||||
# the whole point of the split: a module that declares its own configured
|
||||
# `provider` block can't be called with count/for_each/depends_on and gives
|
||||
# the caller no way to set assume-role, custom endpoints, or aliases.
|
||||
#
|
||||
# `default_tags` set here still flow into every resource the module creates
|
||||
# (provider default_tags propagate through module calls) and merge with the
|
||||
# module's own `litellm:stack` / `managed-by` / var.tags. Use this block for
|
||||
# org-wide tags; use the module's `tags` input for per-deployment tags.
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
"managed-by" = "terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,19 +26,12 @@ env = "stage"
|
|||
# non-empty bucket. Flip to true only for ephemeral / CI stacks.
|
||||
# s3_force_destroy = false
|
||||
|
||||
# Component images. Defaults pin all four to the same GHCR release tag —
|
||||
# bump them together when bumping LiteLLM. Override here to pull from a
|
||||
# private registry or to mix-and-match versions.
|
||||
# gateway_image = "ghcr.io/berriai/litellm-gateway:1.86.0-dev"
|
||||
# backend_image = "ghcr.io/berriai/litellm-backend:1.86.0-dev"
|
||||
# ui_image = "ghcr.io/berriai/litellm-ui:1.86.0-dev"
|
||||
# migrations_image = "ghcr.io/berriai/litellm-migrations:1.86.0-dev"
|
||||
|
||||
# Per-task sizing for the gateway. Defaults are 1 vCPU / 4 GiB / 1 worker.
|
||||
# uvicorn rule of thumb for CPU-bound work is (2 * vCPU) + 1 workers.
|
||||
# gateway_cpu = 1024 # 1024 = 1 vCPU
|
||||
# gateway_memory = 4096 # MiB
|
||||
# gateway_num_workers = 1
|
||||
# Component images and per-task sizing/autoscaling are NOT exposed as
|
||||
# variables in this example (it keeps the curated surface small). They
|
||||
# default to working public GHCR images. To pin images or tune
|
||||
# CPU/memory/workers/autoscaling, set those inputs directly on the
|
||||
# `module "litellm"` block in main.tf — the full list is in
|
||||
# ../../variables.tf — or call the module from your own root config.
|
||||
|
||||
# ---------- proxy_config (mirrors helm gateway.config.proxy_config) ----------
|
||||
# proxy_config = {
|
||||
98
terraform/litellm/aws/examples/default/variables.tf
Normal file
98
terraform/litellm/aws/examples/default/variables.tf
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Curated surface for the one-command deploy path. The module (../../)
|
||||
# exposes far more knobs (per-component CPU/memory, autoscaling, RDS/Redis
|
||||
# sizing, …). To tune those, set them directly on the `module "litellm"`
|
||||
# block in main.tf, or call the module from your own root config. Full
|
||||
# per-variable docs live in ../../variables.tf — the module is the source
|
||||
# of truth; descriptions here are intentionally terse.
|
||||
|
||||
variable "region" {
|
||||
description = "AWS region to deploy into."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "tenant" {
|
||||
description = "Tenant slug — prefix for every resource (<tenant>-litellm-<env>)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "env" {
|
||||
description = "Environment suffix (stage, prod, dev)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "azs" {
|
||||
description = "Availability zones for subnets. At least 2 (RDS + ALB)."
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
# Sensitive — prefer TF_VAR_litellm_master_key / TF_VAR_litellm_license /
|
||||
# TF_VAR_ui_password so values stay out of any committed tfvars file.
|
||||
variable "litellm_master_key" {
|
||||
description = "Pre-existing LITELLM_MASTER_KEY (sk-…). Empty → auto-generated."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "litellm_license" {
|
||||
description = "LiteLLM enterprise license. Empty → OSS-only."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ui_password" {
|
||||
description = "UI admin password. Empty → falls back to LITELLM_MASTER_KEY."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# TLS — provide an ACM cert for production, or opt into HTTP-only for dev.
|
||||
variable "acm_certificate_arn" {
|
||||
description = "ACM cert ARN for the ALB HTTPS listener. Empty → no TLS."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "allow_plaintext_alb" {
|
||||
description = "Opt into HTTP-only ALB (trial/dev only)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "s3_force_destroy" {
|
||||
description = "Allow destroy of a non-empty S3 bucket (ephemeral/CI only)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "proxy_config" {
|
||||
description = "LiteLLM proxy config (contents of config.yaml). Empty → defaults."
|
||||
type = any
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "gateway_extra_env" {
|
||||
description = "Plain-text env vars layered onto the gateway."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "backend_extra_env" {
|
||||
description = "Plain-text env vars layered onto the backend."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "gateway_extra_secrets" {
|
||||
description = "Gateway env vars sourced from Secrets Manager (name → ARN)."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "backend_extra_secrets" {
|
||||
description = "Backend env vars sourced from Secrets Manager (name → ARN)."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
14
terraform/litellm/aws/examples/default/versions.tf
Normal file
14
terraform/litellm/aws/examples/default/versions.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.60"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ data "aws_iam_policy_document" "task_assume" {
|
|||
resource "aws_iam_role" "task_execution" {
|
||||
name = "${local.name}-task-execution"
|
||||
assume_role_policy = data.aws_iam_policy_document.task_assume.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "task_execution" {
|
||||
|
|
@ -59,6 +61,8 @@ data "aws_iam_policy_document" "secrets_access" {
|
|||
resource "aws_iam_policy" "secrets_access" {
|
||||
name = "${local.name}-secrets-access"
|
||||
policy = data.aws_iam_policy_document.secrets_access.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "task_execution_secrets" {
|
||||
|
|
@ -75,6 +79,8 @@ resource "aws_iam_role_policy_attachment" "task_execution_secrets" {
|
|||
resource "aws_iam_role" "task" {
|
||||
name = "${local.name}-task"
|
||||
assume_role_policy = data.aws_iam_policy_document.task_assume.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
|
|
@ -91,6 +97,8 @@ data "aws_iam_policy_document" "rds_iam_connect" {
|
|||
resource "aws_iam_policy" "rds_iam_connect" {
|
||||
name = "${local.name}-rds-iam-connect"
|
||||
policy = data.aws_iam_policy_document.rds_iam_connect.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "task_rds_iam_connect" {
|
||||
|
|
@ -111,4 +119,6 @@ resource "aws_iam_role_policy_attachment" "task_rds_iam_connect" {
|
|||
resource "aws_iam_role" "ui_task" {
|
||||
name = "${local.name}-ui-task"
|
||||
assume_role_policy = data.aws_iam_policy_document.task_assume.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,20 @@ locals {
|
|||
# the stack can reference local.name.
|
||||
name = "${var.tenant}-litellm-${var.env}"
|
||||
|
||||
# This is a reusable module — it declares no `provider` block, so the AWS
|
||||
# provider's `default_tags` is the caller's concern, not ours. To keep the
|
||||
# same per-resource tagging the stack had when it owned the provider, the
|
||||
# module threads `local.tags` onto every taggable resource itself. Callers
|
||||
# may layer org-wide tags on top via their own provider `default_tags`
|
||||
# (those merge with these). `var.tags` is the per-deployment override.
|
||||
tags = merge(
|
||||
{
|
||||
"litellm:stack" = local.name
|
||||
"managed-by" = "terraform"
|
||||
},
|
||||
var.tags,
|
||||
)
|
||||
|
||||
gateway_path_prefixes = [
|
||||
"/v1/chat/*", "/chat/*",
|
||||
"/v1/completions*", "/completions*",
|
||||
|
|
|
|||
|
|
@ -42,4 +42,6 @@ resource "aws_ecs_task_definition" "migrations" {
|
|||
}
|
||||
}
|
||||
}])
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ resource "aws_vpc" "this" {
|
|||
enable_dns_hostnames = true
|
||||
enable_dns_support = true
|
||||
|
||||
tags = { Name = local.name }
|
||||
tags = merge(local.tags, { Name = local.name })
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "this" {
|
||||
vpc_id = aws_vpc.this.id
|
||||
tags = { Name = local.name }
|
||||
tags = merge(local.tags, { Name = local.name })
|
||||
}
|
||||
|
||||
# Public subnets (ALB + NAT). One per AZ.
|
||||
|
|
@ -23,7 +23,7 @@ resource "aws_subnet" "public" {
|
|||
availability_zone = var.azs[count.index]
|
||||
map_public_ip_on_launch = true
|
||||
|
||||
tags = { Name = "${local.name}-public-${var.azs[count.index]}" }
|
||||
tags = merge(local.tags, { Name = "${local.name}-public-${var.azs[count.index]}" })
|
||||
}
|
||||
|
||||
# Private subnets (ECS tasks, RDS, ElastiCache). One per AZ, separate from
|
||||
|
|
@ -34,12 +34,12 @@ resource "aws_subnet" "private" {
|
|||
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
|
||||
availability_zone = var.azs[count.index]
|
||||
|
||||
tags = { Name = "${local.name}-private-${var.azs[count.index]}" }
|
||||
tags = merge(local.tags, { Name = "${local.name}-private-${var.azs[count.index]}" })
|
||||
}
|
||||
|
||||
resource "aws_eip" "nat" {
|
||||
domain = "vpc"
|
||||
tags = { Name = "${local.name}-nat" }
|
||||
tags = merge(local.tags, { Name = "${local.name}-nat" })
|
||||
|
||||
depends_on = [aws_internet_gateway.this]
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ resource "aws_nat_gateway" "this" {
|
|||
allocation_id = aws_eip.nat.id
|
||||
subnet_id = aws_subnet.public[0].id
|
||||
|
||||
tags = { Name = local.name }
|
||||
tags = merge(local.tags, { Name = local.name })
|
||||
|
||||
depends_on = [aws_internet_gateway.this]
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ resource "aws_route_table" "public" {
|
|||
gateway_id = aws_internet_gateway.this.id
|
||||
}
|
||||
|
||||
tags = { Name = "${local.name}-public" }
|
||||
tags = merge(local.tags, { Name = "${local.name}-public" })
|
||||
}
|
||||
|
||||
resource "aws_route_table_association" "public" {
|
||||
|
|
@ -80,7 +80,7 @@ resource "aws_route_table" "private" {
|
|||
nat_gateway_id = aws_nat_gateway.this.id
|
||||
}
|
||||
|
||||
tags = { Name = "${local.name}-private" }
|
||||
tags = merge(local.tags, { Name = "${local.name}-private" })
|
||||
}
|
||||
|
||||
resource "aws_route_table_association" "private" {
|
||||
|
|
@ -119,6 +119,8 @@ resource "aws_security_group" "alb" {
|
|||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_security_group" "tasks" {
|
||||
|
|
@ -141,6 +143,8 @@ resource "aws_security_group" "tasks" {
|
|||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_security_group" "rds" {
|
||||
|
|
@ -155,6 +159,8 @@ resource "aws_security_group" "rds" {
|
|||
protocol = "tcp"
|
||||
security_groups = [aws_security_group.tasks.id]
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_security_group" "redis" {
|
||||
|
|
@ -169,4 +175,6 @@ resource "aws_security_group" "redis" {
|
|||
protocol = "tcp"
|
||||
security_groups = [aws_security_group.tasks.id]
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = merge(
|
||||
{
|
||||
"litellm:stack" = local.name
|
||||
"managed-by" = "terraform"
|
||||
},
|
||||
var.tags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,16 @@
|
|||
resource "aws_db_subnet_group" "this" {
|
||||
name = "${local.name}-db"
|
||||
subnet_ids = aws_subnet.private[*].id
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_parameter_group" "this" {
|
||||
name = "${local.name}-cluster-pg"
|
||||
family = "aurora-postgresql${split(".", var.db_engine_version)[0]}"
|
||||
description = "LiteLLM Aurora Postgres cluster parameters."
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster" "this" {
|
||||
|
|
@ -52,6 +56,8 @@ resource "aws_rds_cluster" "this" {
|
|||
|
||||
backup_retention_period = 7
|
||||
preferred_backup_window = "07:00-09:00"
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_instance" "writer" {
|
||||
|
|
@ -67,6 +73,8 @@ resource "aws_rds_cluster_instance" "writer" {
|
|||
# Promotion tier 0 — first in line during failover, so this instance stays
|
||||
# the writer unless it goes unhealthy.
|
||||
promotion_tier = 0
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_instance" "reader" {
|
||||
|
|
@ -82,4 +90,6 @@ resource "aws_rds_cluster_instance" "reader" {
|
|||
# Higher promotion tier — won't be picked as writer during a failover
|
||||
# unless the writer instance itself is gone.
|
||||
promotion_tier = 15
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
resource "aws_elasticache_subnet_group" "this" {
|
||||
name = "${local.name}-redis"
|
||||
subnet_ids = aws_subnet.private[*].id
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
# Replication group (not aws_elasticache_cluster, which is the
|
||||
|
|
@ -30,4 +32,6 @@ resource "aws_elasticache_replication_group" "this" {
|
|||
transit_encryption_enabled = true
|
||||
|
||||
apply_immediately = true
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ resource "aws_s3_bucket" "this" {
|
|||
# cached responses, archived request logs, and /v1/files storage stay put.
|
||||
# Flip to true only for ephemeral / CI stacks (`var.s3_force_destroy`).
|
||||
force_destroy = var.s3_force_destroy
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "this" {
|
||||
|
|
@ -72,6 +74,8 @@ data "aws_iam_policy_document" "s3_access" {
|
|||
resource "aws_iam_policy" "s3_access" {
|
||||
name = "${local.name}-s3-access"
|
||||
policy = data.aws_iam_policy_document.s3_access.json
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "task_s3_access" {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ resource "aws_secretsmanager_secret" "master_key" {
|
|||
name = "${local.name}-master-key"
|
||||
description = "LITELLM_MASTER_KEY for gateway + backend."
|
||||
recovery_window_in_days = 0
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "master_key" {
|
||||
|
|
@ -40,6 +42,8 @@ resource "aws_secretsmanager_secret" "license" {
|
|||
name = "${local.name}-license"
|
||||
description = "LITELLM_LICENSE for gateway + backend."
|
||||
recovery_window_in_days = 0
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "license" {
|
||||
|
|
@ -59,6 +63,8 @@ resource "aws_secretsmanager_secret" "ui_password" {
|
|||
name = "${local.name}-ui-password"
|
||||
description = "UI_PASSWORD for the backend (UI admin login)."
|
||||
recovery_window_in_days = 0
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "ui_password" {
|
||||
|
|
@ -72,6 +78,8 @@ resource "aws_secretsmanager_secret" "db_master_password" {
|
|||
name = "${local.name}-db-master-password"
|
||||
description = "Aurora master-user password - bootstrap only. Runtime auth is IAM-token."
|
||||
recovery_window_in_days = 0
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "db_master_password" {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ variable "env" {
|
|||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Additional tags merged into the provider default_tags."
|
||||
description = "Per-deployment tags applied to every taggable resource the module creates, on top of the module's own `litellm:stack` / `managed-by` tags. Caller-level provider `default_tags` (if any) merge with these."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,10 +173,11 @@ pair differs:
|
|||
| `acme` | `prod` | `acme-litellm-prod-master-key` |
|
||||
| `globex` | `dev` | `globex-litellm-dev-license` |
|
||||
|
||||
For a per-tenant instance, the only inputs that change are the tenant
|
||||
slug, env, and the two pre-issued secrets:
|
||||
For a per-tenant instance via the example root, the only inputs that
|
||||
change are the tenant slug, env, and the two pre-issued secrets:
|
||||
|
||||
```bash
|
||||
cd terraform/litellm/gcp/examples/default
|
||||
export TF_VAR_litellm_master_key="sk-..." # the tenant's master key
|
||||
export TF_VAR_litellm_license="lic-..." # their LITELLM_LICENSE
|
||||
|
||||
|
|
@ -187,6 +188,10 @@ terraform apply \
|
|||
-var "env=stage"
|
||||
```
|
||||
|
||||
To run *many* tenants from a single config, call the module with
|
||||
`for_each` instead of one root per tenant — only possible because the
|
||||
module declares no provider block (see "Using as a module").
|
||||
|
||||
Both `litellm_master_key` and `litellm_license` are optional:
|
||||
- Omit `litellm_master_key` → the stack auto-generates a random `sk-…`
|
||||
value (trial/dev path).
|
||||
|
|
@ -200,14 +205,22 @@ example files.
|
|||
## Quick start
|
||||
|
||||
```bash
|
||||
cd terraform/litellm/gcp
|
||||
cd terraform/litellm/gcp/examples/default
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
# Edit: project, region, tenant, env, *_image, proxy_config, gateway_extra_secrets.
|
||||
# Edit: project, region, tenant, env, image_registry, proxy_config, gateway_extra_secrets.
|
||||
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
`examples/default/` is a thin root that configures the `google` /
|
||||
`google-beta` providers and calls the module (`../../`). It exposes a
|
||||
curated variable surface; for advanced knobs (per-component
|
||||
CPU/memory/instances, Cloud SQL tier/edition, Memorystore tier,
|
||||
per-component image pins) set them on the `module "litellm"` block in
|
||||
`examples/default/main.tf`, or call the module from your own config — see
|
||||
"Using as a module" below.
|
||||
|
||||
That single apply provisions everything, runs the prisma schema migration via
|
||||
the Cloud Run job (auto-triggered by `bootstrap.tf`), and only then starts the
|
||||
gateway/backend services. When it returns, the stack is serving traffic.
|
||||
|
|
@ -251,6 +264,38 @@ Set `allow_plaintext_lb = true` and leave `lb_domains = []`. Without the
|
|||
flag, plan fails with a clear error pointing at the precondition.
|
||||
Intended for short-lived trial / dev stacks only.
|
||||
|
||||
## Using as a module
|
||||
|
||||
The directory itself is a module with **no `provider` block** — the caller
|
||||
owns provider config. You can call it directly with `for_each` (many
|
||||
tenants from one config), `count`, `depends_on`, or providers configured
|
||||
to impersonate a service account / target a different project:
|
||||
|
||||
```hcl
|
||||
provider "google" {
|
||||
project = "my-gcp-project"
|
||||
region = "us-central1"
|
||||
}
|
||||
provider "google-beta" {
|
||||
project = "my-gcp-project"
|
||||
region = "us-central1"
|
||||
}
|
||||
|
||||
module "litellm" {
|
||||
source = "github.com/BerriAI/litellm//terraform/litellm/gcp?ref=<tag>"
|
||||
|
||||
project = "my-gcp-project"
|
||||
region = "us-central1"
|
||||
tenant = "acme"
|
||||
env = "prod"
|
||||
# ...any of the inputs in variables.tf...
|
||||
}
|
||||
```
|
||||
|
||||
Both the default `google` and `google-beta` configs are inherited by the
|
||||
module automatically through the call — declare both in the caller.
|
||||
Resource labels are controlled by the module's `labels` input.
|
||||
|
||||
## Storage and database retention
|
||||
|
||||
Two opt-in tripwires guard against accidental data loss on
|
||||
|
|
@ -281,8 +326,8 @@ or point them at your own CA.
|
|||
|
||||
| File | What's in it |
|
||||
| ----------------- | -------------------------------------------------------------------- |
|
||||
| `versions.tf` | Terraform + provider version constraints |
|
||||
| `providers.tf` | Google + Google-Beta providers |
|
||||
| `versions.tf` | Terraform + `required_providers` constraints (module declares no provider config) |
|
||||
| `examples/default/` | Thin root: `google` / `google-beta` providers + a call to the module. The one-command deploy path. |
|
||||
| `variables.tf` | All input variables |
|
||||
| `locals.tf` | Path-prefix lists (mirror of `helm/.../ingress.yaml`) + proxy_config helpers |
|
||||
| `network.tf` | VPC, subnet, PSA range, Serverless VPC connector |
|
||||
|
|
|
|||
|
|
@ -45,6 +45,15 @@ resource "google_sql_database_instance" "writer" {
|
|||
}
|
||||
|
||||
deletion_protection = var.cloudsql_deletion_protection
|
||||
|
||||
lifecycle {
|
||||
# disk_autoresize grows storage but never shrinks it. Without this,
|
||||
# the first plan after any auto-grow reads disk_size as a shrink, which
|
||||
# is an immutable change and forces a destroy/recreate of the instance
|
||||
# (full data loss). Set the initial size only; let Cloud SQL own it
|
||||
# thereafter.
|
||||
ignore_changes = [settings[0].disk_size]
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_sql_database_instance" "reader" {
|
||||
|
|
@ -68,6 +77,12 @@ resource "google_sql_database_instance" "reader" {
|
|||
}
|
||||
|
||||
deletion_protection = var.cloudsql_deletion_protection
|
||||
|
||||
lifecycle {
|
||||
# Same autoresize footgun as the writer — the replica grows its disk
|
||||
# independently. Never let a perceived shrink replace the instance.
|
||||
ignore_changes = [settings[0].disk_size]
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_sql_database" "this" {
|
||||
|
|
|
|||
63
terraform/litellm/gcp/examples/default/.terraform.lock.hcl
generated
Normal file
63
terraform/litellm/gcp/examples/default/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/google" {
|
||||
version = "6.50.0"
|
||||
constraints = "~> 6.10"
|
||||
hashes = [
|
||||
"h1:79CwMTsp3Ud1nOl5hFS5mxQHyT0fGVye7pqpU0PPlHI=",
|
||||
"zh:1f3513fcfcbf7ca53d667a168c5067a4dd91a4d4cccd19743e248ff31065503c",
|
||||
"zh:3da7db8fc2c51a77dd958ea8baaa05c29cd7f829bd8941c26e2ea9cb3aadc1e5",
|
||||
"zh:3e09ac3f6ca8111cbb659d38c251771829f4347ab159a12db195e211c76068bb",
|
||||
"zh:7bb9e41c568df15ccf1a8946037355eefb4dfb4e35e3b190808bb7c4abae547d",
|
||||
"zh:81e5d78bdec7778e6d67b5c3544777505db40a826b6eb5abe9b86d4ba396866b",
|
||||
"zh:8d309d020fb321525883f5c4ea864df3d5942b6087f6656d6d8b3a1377f340fc",
|
||||
"zh:93e112559655ab95a523193158f4a4ac0f2bfed7eeaa712010b85ebb551d5071",
|
||||
"zh:d3efe589ffd625b300cef5917c4629513f77e3a7b111c9df65075f76a46a63c7",
|
||||
"zh:d4a4d672bbef756a870d8f32b35925f8ce2ef4f6bbd5b71a3cb764f1b6c85421",
|
||||
"zh:e13a86bca299ba8a118e80d5f84fbdd708fe600ecdceea1a13d4919c068379fe",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
"zh:fec30c095647b583a246c39d557704947195a1b7d41f81e369ba377d997faef6",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/google-beta" {
|
||||
version = "6.50.0"
|
||||
constraints = "~> 6.10"
|
||||
hashes = [
|
||||
"h1:P2GiUJM1frlPtBViwKn1A9V2dVBdGuWcX80w9TdH8ZE=",
|
||||
"zh:18b442bd0a05321d39dda1e9e3f1bdede4e61bc2ac62cc7a67037a3864f75101",
|
||||
"zh:2e387c51455862828bec923a3ec81abf63a4d998da470cf00e09003bda53d668",
|
||||
"zh:3942e708fa84ebe54996086f4b1398cb747fe19cbcd0be07ace528291fb35dee",
|
||||
"zh:496287dd48b34ae6197cb1f887abeafd07c33f389dbe431bb01e24846754cfdd",
|
||||
"zh:6eca885419969ce5c2a706f34dce1f10bde9774757675f2d8a92d12e5a1be390",
|
||||
"zh:710dbef826c3fe7f76f844dae47937e8e4c1279dd9205ec4610be04cf3327244",
|
||||
"zh:777ebf44b24bfc7bdbf770dc089f1a72f143b4718fdedb8c6bd75983115a1ec2",
|
||||
"zh:9c8703bba37b8c7ad857efc3513392c5a096c519397c1cb822d7612f38e4262f",
|
||||
"zh:c4f1d3a73de2702277c99d5348ad6d374705bcfdd367ad964ff4cfd2cf06c281",
|
||||
"zh:eca8df11af3f5a948492d5b8b5d01b4ec705aad10bc30ec1524205508ae28393",
|
||||
"zh:f41e7fd5f2628e8fd6b8ea136366923858f54428d1729898925469b862c275c2",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.9.0"
|
||||
constraints = "~> 3.6"
|
||||
hashes = [
|
||||
"h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=",
|
||||
"zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1",
|
||||
"zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",
|
||||
"zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f",
|
||||
"zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0",
|
||||
"zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61",
|
||||
"zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc",
|
||||
"zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e",
|
||||
"zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef",
|
||||
"zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b",
|
||||
"zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257",
|
||||
"zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04",
|
||||
]
|
||||
}
|
||||
45
terraform/litellm/gcp/examples/default/main.tf
Normal file
45
terraform/litellm/gcp/examples/default/main.tf
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# One-command deploy of the LiteLLM GCP stack.
|
||||
#
|
||||
# cd terraform/litellm/gcp/examples/default
|
||||
# cp terraform.tfvars.example terraform.tfvars # edit it
|
||||
# terraform init
|
||||
# terraform apply
|
||||
#
|
||||
# This root just wires the providers (see providers.tf) to the module. The
|
||||
# module itself (../../) declares no provider, so it can also be consumed
|
||||
# from your own config with count/for_each or impersonated-SA providers:
|
||||
#
|
||||
# module "litellm" {
|
||||
# source = "github.com/BerriAI/litellm//terraform/litellm/gcp?ref=<tag>"
|
||||
# ...
|
||||
# }
|
||||
#
|
||||
# Knobs not surfaced as variables here (per-component sizing/instances,
|
||||
# Cloud SQL tier/edition, Memorystore tier, per-component image overrides)
|
||||
# can be set directly on this block — see ../../variables.tf.
|
||||
module "litellm" {
|
||||
source = "../../"
|
||||
|
||||
project = var.project
|
||||
region = var.region
|
||||
tenant = var.tenant
|
||||
env = var.env
|
||||
|
||||
litellm_master_key = var.litellm_master_key
|
||||
litellm_license = var.litellm_license
|
||||
ui_password = var.ui_password
|
||||
|
||||
image_registry = var.image_registry
|
||||
image_tag = var.image_tag
|
||||
|
||||
lb_domains = var.lb_domains
|
||||
allow_plaintext_lb = var.allow_plaintext_lb
|
||||
cloudsql_deletion_protection = var.cloudsql_deletion_protection
|
||||
gcs_force_destroy = var.gcs_force_destroy
|
||||
|
||||
proxy_config = var.proxy_config
|
||||
gateway_extra_env = var.gateway_extra_env
|
||||
backend_extra_env = var.backend_extra_env
|
||||
gateway_extra_secrets = var.gateway_extra_secrets
|
||||
backend_extra_secrets = var.backend_extra_secrets
|
||||
}
|
||||
59
terraform/litellm/gcp/examples/default/outputs.tf
Normal file
59
terraform/litellm/gcp/examples/default/outputs.tf
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
output "lb_ip" {
|
||||
description = "Global anycast IP of the external load balancer."
|
||||
value = module.litellm.lb_ip
|
||||
}
|
||||
|
||||
output "lb_url" {
|
||||
description = "Proxy URL. Dashboard at /, API at /v1/*."
|
||||
value = module.litellm.lb_url
|
||||
}
|
||||
|
||||
output "gateway_service_url" {
|
||||
description = "Default Cloud Run URL for the gateway (bypasses the LB)."
|
||||
value = module.litellm.gateway_service_url
|
||||
}
|
||||
|
||||
output "backend_service_url" {
|
||||
description = "Default Cloud Run URL for the backend (bypasses the LB)."
|
||||
value = module.litellm.backend_service_url
|
||||
}
|
||||
|
||||
output "ui_service_url" {
|
||||
description = "Default Cloud Run URL for the UI (bypasses the LB)."
|
||||
value = module.litellm.ui_service_url
|
||||
}
|
||||
|
||||
output "cloudsql_writer_ip" {
|
||||
description = "Private IP of the Cloud SQL writer."
|
||||
value = module.litellm.cloudsql_writer_ip
|
||||
}
|
||||
|
||||
output "cloudsql_reader_ip" {
|
||||
description = "Private IP of the Cloud SQL read replica."
|
||||
value = module.litellm.cloudsql_reader_ip
|
||||
}
|
||||
|
||||
output "redis_endpoint" {
|
||||
description = "Memorystore Redis endpoint."
|
||||
value = module.litellm.redis_endpoint
|
||||
}
|
||||
|
||||
output "gcs_bucket" {
|
||||
description = "GCS bucket name."
|
||||
value = module.litellm.gcs_bucket
|
||||
}
|
||||
|
||||
output "master_key_secret_id" {
|
||||
description = "Secret Manager resource ID holding LITELLM_MASTER_KEY."
|
||||
value = module.litellm.master_key_secret_id
|
||||
}
|
||||
|
||||
output "db_password_secret_id" {
|
||||
description = "Secret Manager resource ID holding the Cloud SQL app-user password."
|
||||
value = module.litellm.db_password_secret_id
|
||||
}
|
||||
|
||||
output "migration_run_command" {
|
||||
description = "Break-glass command to re-run the one-off migration job."
|
||||
value = module.litellm.migration_run_command
|
||||
}
|
||||
17
terraform/litellm/gcp/examples/default/providers.tf
Normal file
17
terraform/litellm/gcp/examples/default/providers.tf
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Providers are configured HERE, in the root, not in the module. A module
|
||||
# that declares its own configured `provider` block can't be called with
|
||||
# count/for_each/depends_on and gives the caller no way to set an
|
||||
# impersonated service account, a different project, or aliases.
|
||||
#
|
||||
# The module's resources inherit these default (unaliased) `google` /
|
||||
# `google-beta` configs automatically through the module call, so project
|
||||
# and region set here flow into every resource that doesn't pass its own.
|
||||
provider "google" {
|
||||
project = var.project
|
||||
region = var.region
|
||||
}
|
||||
|
||||
provider "google-beta" {
|
||||
project = var.project
|
||||
region = var.region
|
||||
}
|
||||
|
|
@ -28,14 +28,14 @@ env = "stage"
|
|||
# cloudsql_deletion_protection = true # default: refuse destroy on the DB
|
||||
# gcs_force_destroy = false # default: refuse destroy on a non-empty bucket
|
||||
|
||||
# Component images. Defaults pin all four to the same GHCR release tag —
|
||||
# bump them together when bumping LiteLLM. To use private images, mirror
|
||||
# them into Artifact Registry first — Cloud Run only authenticates against
|
||||
# AR / gcr.io.
|
||||
# gateway_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/gateway:1.86.0-dev"
|
||||
# backend_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/backend:1.86.0-dev"
|
||||
# ui_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/ui:1.86.0-dev"
|
||||
# migrations_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/migrations:1.86.0-dev"
|
||||
# Images. Cloud Run rejects ghcr.io, so a real deploy must point
|
||||
# image_registry at an Artifact Registry remote repo (see README "Image
|
||||
# pulls"); image_tag is applied to all four litellm-* images. Per-component
|
||||
# *_image overrides are NOT exposed here — set them directly on the
|
||||
# `module "litellm"` block in main.tf (see ../../variables.tf) if you need
|
||||
# to mix-and-match versions.
|
||||
# image_registry = "us-central1-docker.pkg.dev/my-gcp-project/litellm/berriai"
|
||||
# image_tag = "v1.86.0-dev"
|
||||
|
||||
# ---------- proxy_config (mirrors helm gateway.config.proxy_config) ----------
|
||||
# proxy_config = {
|
||||
120
terraform/litellm/gcp/examples/default/variables.tf
Normal file
120
terraform/litellm/gcp/examples/default/variables.tf
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Curated surface for the one-command deploy path. The module (../../)
|
||||
# exposes far more knobs (per-component CPU/memory/instances, Cloud SQL
|
||||
# tier/edition, Memorystore tier, per-component image overrides, …). To
|
||||
# tune those, set them directly on the `module "litellm"` block in
|
||||
# main.tf, or call the module from your own root config. Full per-variable
|
||||
# docs live in ../../variables.tf — the module is the source of truth.
|
||||
|
||||
variable "project" {
|
||||
description = "GCP project ID."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "GCP region for VPC, Cloud SQL, Memorystore, Cloud Run, and the LB IP."
|
||||
type = string
|
||||
default = "us-central1"
|
||||
}
|
||||
|
||||
variable "tenant" {
|
||||
description = "Tenant slug — prefix for every resource (<tenant>-litellm-<env>)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "env" {
|
||||
description = "Environment suffix (stage, prod, dev)."
|
||||
type = string
|
||||
}
|
||||
|
||||
# Sensitive — prefer TF_VAR_litellm_master_key / TF_VAR_litellm_license /
|
||||
# TF_VAR_ui_password so values stay out of any committed tfvars file.
|
||||
variable "litellm_master_key" {
|
||||
description = "Pre-existing LITELLM_MASTER_KEY (sk-…). Empty → auto-generated."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "litellm_license" {
|
||||
description = "LiteLLM enterprise license. Empty → OSS-only."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ui_password" {
|
||||
description = "UI admin password. Empty → falls back to LITELLM_MASTER_KEY."
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Image source. Cloud Run rejects ghcr.io, so a real deploy must point
|
||||
# image_registry at an Artifact Registry remote repo (see README "Image
|
||||
# pulls"). Per-component overrides live in ../../variables.tf.
|
||||
variable "image_registry" {
|
||||
description = "Registry path prefix; images composed as <image_registry>/litellm-<component>:<image_tag>."
|
||||
type = string
|
||||
default = "ghcr.io/berriai"
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Tag applied to all four litellm-* images. Bump in lockstep."
|
||||
type = string
|
||||
default = "v1.86.0-dev"
|
||||
}
|
||||
|
||||
# TLS — provide DNS names for a managed cert, or opt into HTTP-only for dev.
|
||||
variable "lb_domains" {
|
||||
description = "DNS names (already pointing at lb_ip) for a Google-managed cert. Empty → no TLS."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "allow_plaintext_lb" {
|
||||
description = "Opt into HTTP-only LB (trial/dev only)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "cloudsql_deletion_protection" {
|
||||
description = "Cloud SQL deletion protection (writer + reader)."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "gcs_force_destroy" {
|
||||
description = "Allow destroy of a non-empty GCS bucket (ephemeral/CI only)."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "proxy_config" {
|
||||
description = "LiteLLM proxy config (contents of config.yaml). Empty → defaults."
|
||||
type = any
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "gateway_extra_env" {
|
||||
description = "Plain-text env vars layered onto the gateway."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "backend_extra_env" {
|
||||
description = "Plain-text env vars layered onto the backend."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "gateway_extra_secrets" {
|
||||
description = "Gateway env vars sourced from Secret Manager (name → secret resource ID)."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "backend_extra_secrets" {
|
||||
description = "Backend env vars sourced from Secret Manager (name → secret resource ID)."
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
18
terraform/litellm/gcp/examples/default/versions.tf
Normal file
18
terraform/litellm/gcp/examples/default/versions.tf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "~> 6.10"
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = "~> 6.10"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,11 +160,22 @@ resource "google_compute_global_forwarding_rule" "http" {
|
|||
|
||||
resource "google_compute_managed_ssl_certificate" "this" {
|
||||
count = local.tls_enabled ? 1 : 0
|
||||
name = "${local.name}-cert"
|
||||
|
||||
# A managed cert's `domains` is immutable, so changing var.lb_domains
|
||||
# forces replacement, and the cert is referenced by the HTTPS target
|
||||
# proxy — a destroy-then-create replacement fails with
|
||||
# `resourceInUseByAnotherResource`. Hashing the domains into the name
|
||||
# makes the name change with the domain set, so create_before_destroy
|
||||
# builds the new cert + repoints the proxy before deleting the old one.
|
||||
name = "${local.name}-cert-${substr(sha1(join(",", var.lb_domains)), 0, 8)}"
|
||||
|
||||
managed {
|
||||
domains = var.lb_domains
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_target_https_proxy" "this" {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
provider "google" {
|
||||
project = var.project
|
||||
region = var.region
|
||||
}
|
||||
|
||||
provider "google-beta" {
|
||||
project = var.project
|
||||
region = var.region
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue