diff --git a/deploy/tofu/aws/README.md b/deploy/tofu/aws/README.md index 64a2f84..95b3cd9 100644 --- a/deploy/tofu/aws/README.md +++ b/deploy/tofu/aws/README.md @@ -110,11 +110,13 @@ have no fixed range. `include_domain` takes `route53_zone_id` plus two labels, `dashboard_subdomain` and `api_subdomain`, relative to that zone (`app` and `api` on `example.com` give `app.example.com` and `api.example.com`; an empty label means the apex). +An optional `inference_proxy_subdomain` adds a third name for gateway mode; +reserving it up front costs nothing and saves reissuing the certificate later. The zone's name is read back, so nothing repeats the domain. The load balancer only exists after the chart's Ingress is installed, so the records are a second apply: `tofu apply -var alb_hostname=`. `certificate_arn`, -`dashboard_hostname` and `api_hostname` are output for the chart and -`IngressClassParams`. +`dashboard_hostname`, `api_hostname` and `inference_proxy_hostname` are output +for the chart and `IngressClassParams`. ## Naming and tags diff --git a/deploy/tofu/aws/domain.tf b/deploy/tofu/aws/domain.tf index faf3800..ab90475 100644 --- a/deploy/tofu/aws/domain.tf +++ b/deploy/tofu/aws/domain.tf @@ -15,6 +15,10 @@ data "aws_route53_zone" "blue" { condition = var.dashboard_subdomain != var.api_subdomain error_message = "dashboard_subdomain and api_subdomain must differ; both cannot be the same name." } + precondition { + condition = var.inference_proxy_subdomain == "" || (var.inference_proxy_subdomain != var.dashboard_subdomain && var.inference_proxy_subdomain != var.api_subdomain) + error_message = "inference_proxy_subdomain must differ from dashboard_subdomain and api_subdomain." + } } } @@ -22,12 +26,20 @@ locals { zone_name = var.include_domain ? trimsuffix(data.aws_route53_zone.blue[0].name, ".") : "" dashboard_hostname = var.include_domain ? (var.dashboard_subdomain == "" ? local.zone_name : "${var.dashboard_subdomain}.${local.zone_name}") : null api_hostname = var.include_domain ? (var.api_subdomain == "" ? local.zone_name : "${var.api_subdomain}.${local.zone_name}") : null + # Optional third name for gateway mode. Reserving it costs nothing, so a + # deployment can add the inference proxy later without reissuing the certificate. + inference_proxy_hostname = var.include_domain && var.inference_proxy_subdomain != "" ? "${var.inference_proxy_subdomain}.${local.zone_name}" : null + # Every hostname the load balancer answers for, keyed for the alias records. + hostnames = merge( + var.include_domain ? { dashboard = local.dashboard_hostname, api = local.api_hostname } : {}, + local.inference_proxy_hostname != null ? { inference_proxy = local.inference_proxy_hostname } : {}, + ) } resource "aws_acm_certificate" "blue" { count = var.include_domain ? 1 : 0 domain_name = local.dashboard_hostname - subject_alternative_names = [local.api_hostname] + subject_alternative_names = compact([local.api_hostname, local.inference_proxy_hostname]) validation_method = "DNS" lifecycle { create_before_destroy = true } @@ -65,10 +77,7 @@ data "aws_elb_hosted_zone_id" "alb" { resource "aws_route53_record" "blue" { #checkov:skip=CKV2_AWS_23:the alias target is var.alb_hostname, an ALB created outside this module by the AWS Load Balancer Controller; checkov renders the variable to its "" default and so misses the check's own var. escape hatch - for_each = var.include_domain && var.alb_hostname != "" ? { - dashboard = local.dashboard_hostname - api = local.api_hostname - } : {} + for_each = var.alb_hostname != "" ? local.hostnames : {} zone_id = var.route53_zone_id name = each.value diff --git a/deploy/tofu/aws/outputs.tf b/deploy/tofu/aws/outputs.tf index 2d2480a..cf56e66 100644 --- a/deploy/tofu/aws/outputs.tf +++ b/deploy/tofu/aws/outputs.tf @@ -11,6 +11,7 @@ output "kms_key_arn" { value = aws_kms_key.blue.arn } output "certificate_arn" { value = var.include_domain ? aws_acm_certificate_validation.blue[0].certificate_arn : null } output "dashboard_hostname" { value = local.dashboard_hostname } output "api_hostname" { value = local.api_hostname } +output "inference_proxy_hostname" { value = local.inference_proxy_hostname } output "cluster_name" { value = local.cluster_name } output "cluster_endpoint" { value = local.create_cluster ? aws_eks_cluster.blue[0].endpoint : null } output "vpc_id" { value = local.vpc_id } diff --git a/deploy/tofu/aws/terraform.tfvars.example b/deploy/tofu/aws/terraform.tfvars.example index f4698ef..c8a7e54 100644 --- a/deploy/tofu/aws/terraform.tfvars.example +++ b/deploy/tofu/aws/terraform.tfvars.example @@ -39,6 +39,7 @@ environment = "production" # route53_zone_id = "Z0123456789EXAMPLE" # dashboard_subdomain = "app" # api_subdomain = "api" +# inference_proxy_subdomain = "iproxy" # optional; gateway mode's third name kubernetes_namespace = "blue" kubernetes_service_account = "blue" diff --git a/deploy/tofu/aws/variables.tf b/deploy/tofu/aws/variables.tf index b2943a2..ab741a6 100644 --- a/deploy/tofu/aws/variables.tf +++ b/deploy/tofu/aws/variables.tf @@ -58,6 +58,11 @@ variable "api_subdomain" { default = "api" description = "Control API label relative to the zone. Empty means the zone apex; it must differ from dashboard_subdomain." } +variable "inference_proxy_subdomain" { + type = string + default = "" + description = "Inference proxy label relative to the zone, used in gateway mode. Empty means no proxy hostname: it is left off the certificate and gets no record." +} variable "alb_hostname" { type = string default = ""