Skip to content

Commit d5d4277

Browse files
committed
Add support for a multi-region LB for tiles_tlog
This change adds submodules to tiles_tlog: 1. tiles_tlog/shared creates common resources like health checks and security policies that only need to be created once, universally, no matter the kind of deployment. 2. tiles_tlog/global creates another set of load balancer resources that support global write endpoints that may route to shards living in any given region. The current region-specific load balancer resources remain the same, because read traffic will always need to use the regional endpoint and because the write endpoint makes it easier to check region-specific liveness. Signed-off-by: Colleen Murphy <colleenmurphy@google.com>
1 parent ceecd69 commit d5d4277

12 files changed

Lines changed: 853 additions & 162 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/**
2+
* Copyright 2026 The Sigstore Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "shared" {
18+
source = "../shared"
19+
20+
project_id = var.project_id
21+
dns_subdomain_name = var.dns_subdomain_name
22+
service_health_check_path = var.service_health_check_path
23+
max_req_content_length = var.max_req_content_length
24+
max_req_content_length_description = var.max_req_content_length_description
25+
enable_healthcheck_logging = var.enable_healthcheck_logging
26+
http_grpc_qpm_rate_limit = var.http_grpc_qpm_rate_limit
27+
enable_adaptive_protection = var.enable_adaptive_protection
28+
create_grpc_health_check = var.grpc_write_path == "" ? false : true
29+
}
30+
31+
locals {
32+
hostname = trimsuffix("${var.dns_subdomain_name}.${var.dns_domain_name}", ".")
33+
prefix = replace(var.dns_subdomain_name, ".", "-")
34+
}
35+
36+
resource "google_dns_record_set" "A_tlog" {
37+
name = "${var.dns_subdomain_name}.${var.dns_domain_name}"
38+
type = "A"
39+
ttl = 60
40+
41+
project = var.project_id
42+
managed_zone = var.dns_zone_name
43+
44+
rrdatas = [google_compute_global_address.global_lb_ipv4.address]
45+
}
46+
47+
resource "google_certificate_manager_dns_authorization" "tlog_auth" {
48+
name = "${local.prefix}-dns-auth"
49+
domain = local.hostname
50+
}
51+
52+
resource "google_dns_record_set" "CNAME_auth_tlog" {
53+
project = var.project_id
54+
name = google_certificate_manager_dns_authorization.tlog_auth.dns_resource_record[0].name
55+
type = google_certificate_manager_dns_authorization.tlog_auth.dns_resource_record[0].type
56+
ttl = 60
57+
managed_zone = var.dns_zone_name
58+
rrdatas = [google_certificate_manager_dns_authorization.tlog_auth.dns_resource_record[0].data]
59+
}
60+
61+
resource "google_compute_global_address" "global_lb_ipv4" {
62+
name = "${local.prefix}-global-ext-lb"
63+
address_type = "EXTERNAL"
64+
project = var.project_id
65+
}
66+
67+
locals {
68+
http_neg_map = { for neg in var.active_http_negs : "${neg.name}-${neg.zone}" => neg }
69+
grpc_neg_map = { for neg in var.active_grpc_negs : "${neg.name}-${neg.zone}" => neg }
70+
}
71+
72+
data "google_compute_network_endpoint_group" "k8s_http_neg" {
73+
for_each = local.http_neg_map
74+
75+
name = each.value.name
76+
project = var.project_id
77+
zone = each.value.zone
78+
}
79+
80+
data "google_compute_network_endpoint_group" "k8s_grpc_neg" {
81+
for_each = local.grpc_neg_map
82+
83+
name = each.value.name
84+
project = var.project_id
85+
zone = each.value.zone
86+
}
87+
88+
resource "google_compute_backend_service" "k8s_http_backend_service" {
89+
name = "${local.prefix}-global-k8s-neg-backend-service"
90+
project = var.project_id
91+
92+
load_balancing_scheme = "EXTERNAL_MANAGED"
93+
port_name = "http"
94+
protocol = "HTTP"
95+
96+
connection_draining_timeout_sec = 15
97+
health_checks = [module.shared.http_health_check_id]
98+
99+
dynamic "backend" {
100+
for_each = data.google_compute_network_endpoint_group.k8s_http_neg
101+
iterator = neg
102+
103+
content {
104+
group = neg.value.id
105+
balancing_mode = "RATE"
106+
max_rate_per_endpoint = var.backend_service_max_rps
107+
}
108+
}
109+
110+
security_policy = module.shared.security_policy_id
111+
112+
log_config {
113+
enable = var.enable_backend_service_logging
114+
}
115+
}
116+
117+
resource "google_compute_backend_service" "k8s_grpc_backend_service" {
118+
count = var.grpc_write_path == "" ? 0 : 1
119+
name = "${local.prefix}-global-k8s-grpc-neg-backend-service"
120+
project = var.project_id
121+
122+
load_balancing_scheme = "EXTERNAL_MANAGED"
123+
port_name = "grpc"
124+
protocol = "HTTP2"
125+
126+
connection_draining_timeout_sec = 15
127+
health_checks = module.shared.grpc_health_check_id != "" ? [module.shared.grpc_health_check_id] : []
128+
129+
dynamic "backend" {
130+
for_each = data.google_compute_network_endpoint_group.k8s_grpc_neg
131+
iterator = neg
132+
133+
content {
134+
group = neg.value.id
135+
balancing_mode = "RATE"
136+
max_rate_per_endpoint = var.backend_service_max_rps
137+
}
138+
}
139+
140+
security_policy = module.shared.security_policy_id
141+
142+
log_config {
143+
enable = var.enable_backend_service_logging
144+
}
145+
}
146+
147+
resource "google_compute_url_map" "url_map" {
148+
name = "${local.prefix}-global-lb"
149+
project = var.project_id
150+
151+
default_service = google_compute_backend_service.k8s_http_backend_service.id
152+
153+
host_rule {
154+
hosts = [local.hostname]
155+
path_matcher = "global"
156+
}
157+
158+
path_matcher {
159+
name = "global"
160+
default_service = google_compute_backend_service.k8s_http_backend_service.id
161+
dynamic "route_rules" {
162+
for_each = length(var.active_http_negs) > 0 ? [1] : []
163+
164+
content {
165+
priority = 1
166+
service = google_compute_backend_service.k8s_http_backend_service.id
167+
match_rules {
168+
path_template_match = var.http_write_path
169+
}
170+
match_rules {
171+
full_path_match = "/healthz"
172+
}
173+
}
174+
}
175+
dynamic "route_rules" {
176+
for_each = length(var.active_grpc_negs) > 0 && var.grpc_write_path != "" ? [1] : []
177+
178+
content {
179+
priority = 2
180+
service = google_compute_backend_service.k8s_grpc_backend_service[0].id
181+
match_rules {
182+
path_template_match = var.grpc_write_path
183+
}
184+
}
185+
}
186+
}
187+
}
188+
189+
resource "google_certificate_manager_certificate" "ssl_certificate" {
190+
name = "${local.prefix}-global-ssl-cert"
191+
project = var.project_id
192+
193+
managed {
194+
domains = [local.hostname]
195+
dns_authorizations = [
196+
google_certificate_manager_dns_authorization.tlog_auth.id
197+
]
198+
}
199+
}
200+
201+
resource "google_certificate_manager_certificate_map" "tlog_certificate_map" {
202+
name = "${local.prefix}-cert-map"
203+
}
204+
205+
resource "google_certificate_manager_certificate_map_entry" "tlog_certificate_map_entry" {
206+
name = "${local.prefix}-cert-map-entry"
207+
map = google_certificate_manager_certificate_map.tlog_certificate_map.name
208+
certificates = [google_certificate_manager_certificate.ssl_certificate.id]
209+
hostname = local.hostname
210+
}
211+
212+
resource "google_compute_target_https_proxy" "lb_proxy" {
213+
name = "${local.prefix}-global-https-proxy"
214+
project = var.project_id
215+
216+
url_map = google_compute_url_map.url_map.id
217+
218+
ssl_policy = module.shared.ssl_policy_id
219+
certificate_map = "//certificatemanager.googleapis.com/${google_certificate_manager_certificate_map.tlog_certificate_map.id}"
220+
}
221+
222+
resource "google_compute_global_forwarding_rule" "https_forwarding_rule" {
223+
name = "${local.prefix}-global-https-forwarding-rule"
224+
project = var.project_id
225+
226+
ip_address = google_compute_global_address.global_lb_ipv4.address
227+
target = google_compute_target_https_proxy.lb_proxy.id
228+
port_range = "443"
229+
load_balancing_scheme = "EXTERNAL_MANAGED"
230+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright 2026 The Sigstore Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
output "http_health_check_id" {
18+
value = module.shared.http_health_check_id
19+
}
20+
21+
output "grpc_health_check_id" {
22+
value = module.shared.grpc_health_check_id
23+
}
24+
25+
output "security_policy_id" {
26+
value = module.shared.security_policy_id
27+
}
28+
29+
output "bucket_security_policy_id" {
30+
value = module.shared.bucket_security_policy_id
31+
}
32+
33+
output "ssl_policy_id" {
34+
value = module.shared.ssl_policy_id
35+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Copyright 2026 The Sigstore Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
variable "project_id" {
18+
type = string
19+
default = ""
20+
validation {
21+
condition = length(var.project_id) > 0
22+
error_message = "Must specify project_id variable."
23+
}
24+
}
25+
26+
variable "dns_zone_name" {
27+
description = "Name of DNS Zone object in Google Cloud DNS"
28+
type = string
29+
}
30+
31+
variable "dns_domain_name" {
32+
description = "Name of DNS domain name in Google Cloud DNS"
33+
type = string
34+
}
35+
36+
variable "dns_subdomain_name" {
37+
description = "Subdomain name for the service, e.g. 'v2.rekor' or 'tessera.ct'"
38+
type = string
39+
}
40+
41+
variable "active_http_negs" {
42+
type = list(object({
43+
name = string
44+
zone = string
45+
}))
46+
description = "List of objects containing the names and zones of active HTTP NEGs across all shards and regions to route write traffic to."
47+
default = []
48+
}
49+
50+
variable "active_grpc_negs" {
51+
type = list(object({
52+
name = string
53+
zone = string
54+
}))
55+
description = "List of objects containing the names and zones of active gRPC NEGs across all shards and regions to route write traffic to."
56+
default = []
57+
}
58+
59+
variable "backend_service_max_rps" {
60+
description = "Max requests per second that a single backend instance can handle."
61+
type = number
62+
default = 5
63+
}
64+
65+
variable "enable_backend_service_logging" {
66+
description = "Whether to enable logging for the HTTP backend service."
67+
type = bool
68+
default = true
69+
}
70+
71+
variable "http_write_path" {
72+
description = "The path for write requests on HTTP"
73+
type = string
74+
}
75+
76+
variable "grpc_write_path" {
77+
description = "The path for write requests on GRPC"
78+
type = string
79+
default = ""
80+
}
81+
82+
variable "service_health_check_path" {
83+
description = "HTTP URL request path for the service health check"
84+
type = string
85+
default = "/healthz"
86+
}
87+
88+
variable "max_req_content_length" {
89+
description = "maximum request content length in bytes for the write path"
90+
type = number
91+
default = 8388608 // 8 MB
92+
}
93+
94+
variable "max_req_content_length_description" {
95+
description = "maximum request content length, used only for security policy description"
96+
type = string
97+
default = "8MB"
98+
}
99+
100+
variable "enable_healthcheck_logging" {
101+
description = "whether to enable logging for the HTTP and gRPC health checks"
102+
type = bool
103+
default = true
104+
}
105+
106+
variable "http_grpc_qpm_rate_limit" {
107+
description = "count of write requests per minute allowed to HTTP and gRPC backends"
108+
type = number
109+
default = 600 // 10 QPS
110+
}
111+
112+
variable "enable_adaptive_protection" {
113+
description = "whether to enable layer 7 DDoS adaptive protection"
114+
type = bool
115+
default = true
116+
}

0 commit comments

Comments
 (0)