diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index d3b0f7b8..87fdb2a5 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -143,6 +143,21 @@ steps: - verify internal-lb-http name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestInternalLbCloudRun --stage teardown --verbose'] +- id: apply internal-lb-http gce-mig + waitFor: + - create + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestInternalLbGCEMIG --stage apply --verbose'] +- id: verify internal-lb-http gce-mig + waitFor: + - apply internal-lb-http gce-mig + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'sleep 360 && cft test run TestInternalLbGCEMIG --stage verify --verbose'] +- id: teardown internal-lb-http gce-mig + waitFor: + - verify internal-lb-http gce-mig + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestInternalLbGCEMIG --stage teardown --verbose'] tags: - 'ci' - 'integration' diff --git a/examples/internal-lb-cloud-run/readme.md b/examples/internal-lb-cloud-run/readme.md index 444c6e16..d6d21cd7 100644 --- a/examples/internal-lb-cloud-run/readme.md +++ b/examples/internal-lb-cloud-run/readme.md @@ -1,4 +1,4 @@ -# HTTP Internal Regional Load Balancer Example +# HTTP Internal Cross-Regional Load Balancer Example This example creates a simple application with below components. @@ -17,4 +17,10 @@ The forwarding rules and its dependecies are created as part of `frontend` modul |------|-------------|------|---------|:--------:| | project\_id | n/a | `string` | n/a | yes | +## Outputs + +| Name | Description | +|------|-------------| +| external\_cloudrun\_uris | The uris of the publicaly accesible cloud-run services | + diff --git a/examples/internal-lb-gce-mig/main.tf b/examples/internal-lb-gce-mig/main.tf new file mode 100644 index 00000000..b8f9a517 --- /dev/null +++ b/examples/internal-lb-gce-mig/main.tf @@ -0,0 +1,255 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +provider "google" { + project = var.project_id +} + +provider "google-beta" { + project = var.project_id +} + +module "internal-lb-network" { + source = "terraform-google-modules/network/google//modules/vpc" + version = "~> 10.0.0" + project_id = var.project_id + network_name = "int-lb-mig-network" + auto_create_subnetworks = false +} + +module "internal-lb-subnet" { + source = "terraform-google-modules/network/google//modules/subnets" + version = "~> 10.0.0" + + subnets = [ + { + subnet_name = "int-lb-mig-subnet-a" + subnet_ip = "10.1.2.0/24" + subnet_region = "us-east1" + }, + { + subnet_name = "int-lb-mig-proxy-only-subnet-a" + subnet_ip = "10.129.0.0/23" + subnet_region = "us-east1" + purpose = "GLOBAL_MANAGED_PROXY" + role = "ACTIVE" + }, + { + subnet_name = "int-lb-mig-subnet-b" + subnet_ip = "10.1.3.0/24" + subnet_region = "us-central1" + }, + { + subnet_name = "int-lb-mig-proxy-only-subnet-b", + subnet_ip = "10.130.0.0/23" + subnet_region = "us-central1" + purpose = "GLOBAL_MANAGED_PROXY" + role = "ACTIVE" + } + ] + + network_name = module.internal-lb-network.network_name + project_id = var.project_id + depends_on = [module.internal-lb-network] +} + +module "instance-template-region-a" { + source = "terraform-google-modules/vm/google//modules/instance_template" + version = "~> 13.0" + + project_id = var.project_id + region = "us-east1" + source_image_project = "debian-cloud" + source_image = "debian-12" + network = module.internal-lb-network.network_name + subnetwork = module.internal-lb-subnet.subnets["us-east1/int-lb-mig-subnet-a"].name + access_config = [{ network_tier : "PREMIUM" }] + name_prefix = "instance-template-region-a" + startup_script = < +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| project\_id | n/a | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| external\_cloudrun\_uris | The uris of the publicaly accesible cloud-run services | + + diff --git a/examples/internal-lb-gce-mig/variables.tf b/examples/internal-lb-gce-mig/variables.tf new file mode 100644 index 00000000..419e3a19 --- /dev/null +++ b/examples/internal-lb-gce-mig/variables.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + type = string +} diff --git a/metadata.yaml b/metadata.yaml index 0136fc72..ddf68ac9 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -60,6 +60,8 @@ spec: location: examples/https-redirect - name: internal-lb-cloud-run location: examples/internal-lb-cloud-run + - name: internal-lb-gce-mig + location: examples/internal-lb-gce-mig - name: lb-http-separate-frontend-and-backend location: examples/lb-http-separate-frontend-and-backend - name: mig-nat-http-lb diff --git a/modules/backend/README.md b/modules/backend/README.md index 5c5616c7..c8b59193 100644 --- a/modules/backend/README.md +++ b/modules/backend/README.md @@ -17,6 +17,7 @@ This module creates `google_compute_backend_service` resource and its dependenci | enable\_cdn | Enable Cloud CDN for this BackendService. | `bool` | `false` | no | | firewall\_networks | Names of the networks to create firewall rules in | `list(string)` |
[
"default"
]
| no | | firewall\_projects | Names of the projects to create firewall rules in | `list(string)` |
[
"default"
]
| no | +| firewall\_source\_ranges | Source ranges for the global Application Load Balancer's proxies. This list should contain the `ip_cidr_range` of each GLOBAL\_MANAGED\_PROXY subnet. | `list(string)` |
[
"10.127.0.0/23"
]
| no | | groups | The list of backend instance group which serves the traffic. |
list(object({
group = string
description = optional(string)

balancing_mode = optional(string)
capacity_scaler = optional(number)
max_connections = optional(number)
max_connections_per_instance = optional(number)
max_connections_per_endpoint = optional(number)
max_rate = optional(number)
max_rate_per_instance = optional(number)
max_rate_per_endpoint = optional(number)
max_utilization = optional(number)
}))
| `[]` | no | | health\_check | Input for creating HttpHealthCheck or HttpsHealthCheck resource for health checking this BackendService. A health check must be specified unless the backend service uses an internet or serverless NEG as a backend. |
object({
host = optional(string, null)
request_path = optional(string, null)
request = optional(string, null)
response = optional(string, null)
port = optional(number, null)
port_name = optional(string, null)
proxy_header = optional(string, null)
port_specification = optional(string, null)
protocol = optional(string, null)
check_interval_sec = optional(number, 5)
timeout_sec = optional(number, 5)
healthy_threshold = optional(number, 2)
unhealthy_threshold = optional(number, 2)
logging = optional(bool, false)
})
| `null` | no | | host\_path\_mappings | The list of host/path for which traffic could be sent to the backend service |
list(object({
host = string
path = string
}))
|
[
{
"host": "*",
"path": "/*"
}
]
| no | diff --git a/modules/backend/main.tf b/modules/backend/main.tf index 09864b33..bbae368b 100644 --- a/modules/backend/main.tf +++ b/modules/backend/main.tf @@ -61,7 +61,7 @@ resource "google_compute_backend_service" "default" { dynamic "backend" { for_each = toset(var.serverless_neg_backends) content { - group = google_compute_region_network_endpoint_group.serverless_negs["neg-${var.name}-${backend.value.region}"].id + group = google_compute_region_network_endpoint_group.serverless_negs["neg-${var.name}-${backend.value.service_name}-${backend.value.region}"].id } } @@ -157,7 +157,7 @@ resource "google_compute_backend_service" "default" { resource "google_compute_region_network_endpoint_group" "serverless_negs" { for_each = { for serverless_neg_backend in var.serverless_neg_backends : - "neg-${var.name}-${serverless_neg_backend.region}" => serverless_neg_backend } + "neg-${var.name}-${serverless_neg_backend.service_name}-${serverless_neg_backend.region}" => serverless_neg_backend } provider = google-beta @@ -289,3 +289,24 @@ resource "google_compute_firewall" "default-hc" { ports = var.health_check.port != null ? [var.health_check.port] : null } } + +resource "google_compute_firewall" "allow_proxy" { + count = var.health_check != null ? length(var.firewall_networks) : 0 + project = length(var.firewall_networks) == 1 && var.firewall_projects[0] == "default" ? var.project_id : var.firewall_projects[count.index] + name = "${var.name}-fw-allow-proxies-${count.index}" + network = var.firewall_networks[count.index] + source_ranges = var.firewall_source_ranges + target_tags = length(var.target_tags) > 0 ? var.target_tags : null + allow { + ports = ["443"] + protocol = "tcp" + } + allow { + ports = ["80"] + protocol = "tcp" + } + allow { + ports = ["8080"] + protocol = "tcp" + } +} diff --git a/modules/backend/metadata.yaml b/modules/backend/metadata.yaml index 4506fe82..ca4082f6 100644 --- a/modules/backend/metadata.yaml +++ b/modules/backend/metadata.yaml @@ -52,6 +52,8 @@ spec: location: examples/https-redirect - name: internal-lb-cloud-run location: examples/internal-lb-cloud-run + - name: internal-lb-gce-mig + location: examples/internal-lb-gce-mig - name: lb-http-separate-frontend-and-backend location: examples/lb-http-separate-frontend-and-backend - name: mig-nat-http-lb @@ -286,6 +288,11 @@ spec: description: List of target service accounts for health check firewall rule. Exactly one of target_tags or target_service_accounts should be specified. varType: list(string) defaultValue: [] + - name: firewall_source_ranges + description: Source ranges for the global Application Load Balancer's proxies. This list should contain the `ip_cidr_range` of each GLOBAL_MANAGED_PROXY subnet. + varType: list(string) + defaultValue: + - 10.127.0.0/23 outputs: - name: backend_service_info description: Host, path and backend service mapping diff --git a/modules/backend/variables.tf b/modules/backend/variables.tf index 882aff6d..a79cfae3 100644 --- a/modules/backend/variables.tf +++ b/modules/backend/variables.tf @@ -140,6 +140,11 @@ variable "serverless_neg_backends" { service_version = optional(string) })) default = [] + + validation { + condition = length(distinct([for backend in var.serverless_neg_backends : backend.region])) == length(var.serverless_neg_backends) + error_message = "The 'region' within each 'serverless_neg_backends' block must be unique." + } } variable "iap_config" { @@ -269,3 +274,9 @@ variable "target_service_accounts" { type = list(string) default = [] } + +variable "firewall_source_ranges" { + description = "Source ranges for the global Application Load Balancer's proxies. This list should contain the `ip_cidr_range` of each GLOBAL_MANAGED_PROXY subnet." + type = list(string) + default = ["10.127.0.0/23"] +} diff --git a/modules/dynamic_backends/metadata.yaml b/modules/dynamic_backends/metadata.yaml index 6106d47b..58de844d 100644 --- a/modules/dynamic_backends/metadata.yaml +++ b/modules/dynamic_backends/metadata.yaml @@ -52,6 +52,8 @@ spec: location: examples/https-redirect - name: internal-lb-cloud-run location: examples/internal-lb-cloud-run + - name: internal-lb-gce-mig + location: examples/internal-lb-gce-mig - name: lb-http-separate-frontend-and-backend location: examples/lb-http-separate-frontend-and-backend - name: mig-nat-http-lb diff --git a/modules/frontend/README.md b/modules/frontend/README.md index e2552a99..d32eb9c7 100644 --- a/modules/frontend/README.md +++ b/modules/frontend/README.md @@ -25,7 +25,7 @@ This module creates `HTTP(S) forwarding rule` and its dependencies. This modules | load\_balancing\_scheme | Load balancing scheme type (EXTERNAL for classic external load balancer, EXTERNAL\_MANAGED for Envoy-based load balancer, INTERNAL\_MANAGED for internal load balancer and INTERNAL\_SELF\_MANAGED for traffic director) | `string` | `"EXTERNAL_MANAGED"` | no | | managed\_ssl\_certificate\_domains | Create Google-managed SSL certificates for specified domains. Requires `ssl` to be set to `true` | `list(string)` | `[]` | no | | name | Name for the forwarding rule and prefix for supporting resources | `string` | n/a | yes | -| network | Network for internal load balancer | `string` | `"default"` | no | +| network | VPC network for the forwarding rule. The VPC network should have exactly one GLOBAL\_MANAGED\_PROXY subnetwork for every region where the forwarding rule is to be configured. Please go to the subnets tab of your VPC network and check if a GLOBAL\_MANAGED\_PROXY subnet exists under the `Reserved proxy-only subnets for load balancing` section. If a GLOBAL\_MANAGED\_PROXY subnet doesn't exist, create one for each required region. | `string` | `"default"` | no | | private\_key | Content of the private SSL key. Requires `ssl` to be set to `true` and `create_ssl_certificate` set to `true` | `string` | `null` | no | | project\_id | The project to deploy to, if not set the default provider project is used. | `string` | n/a | yes | | quic | Specifies the QUIC override policy for this resource. Set true to enable HTTP/3 and Google QUIC support, false to disable both. Defaults to null which enables support for HTTP/3 only. | `bool` | `null` | no | diff --git a/modules/frontend/metadata.yaml b/modules/frontend/metadata.yaml index 1abd52e4..1fa724d1 100644 --- a/modules/frontend/metadata.yaml +++ b/modules/frontend/metadata.yaml @@ -52,6 +52,8 @@ spec: location: examples/https-redirect - name: internal-lb-cloud-run location: examples/internal-lb-cloud-run + - name: internal-lb-gce-mig + location: examples/internal-lb-gce-mig - name: lb-http-separate-frontend-and-backend location: examples/lb-http-separate-frontend-and-backend - name: mig-nat-http-lb @@ -170,7 +172,7 @@ spec: varType: string defaultValue: EXTERNAL_MANAGED - name: network - description: Network for internal load balancer + description: VPC network for the forwarding rule. The VPC network should have exactly one GLOBAL_MANAGED_PROXY subnetwork for every region where the forwarding rule is to be configured. Please go to the subnets tab of your VPC network and check if a GLOBAL_MANAGED_PROXY subnet exists under the `Reserved proxy-only subnets for load balancing` section. If a GLOBAL_MANAGED_PROXY subnet doesn't exist, create one for each required region. varType: string defaultValue: default - name: server_tls_policy diff --git a/modules/frontend/variables.tf b/modules/frontend/variables.tf index 7f11bc84..664b458a 100644 --- a/modules/frontend/variables.tf +++ b/modules/frontend/variables.tf @@ -162,7 +162,7 @@ variable "load_balancing_scheme" { } variable "network" { - description = "Network for internal load balancer" + description = "VPC network for the forwarding rule. The VPC network should have exactly one GLOBAL_MANAGED_PROXY subnetwork for every region where the forwarding rule is to be configured. Please go to the subnets tab of your VPC network and check if a GLOBAL_MANAGED_PROXY subnet exists under the `Reserved proxy-only subnets for load balancing` section. If a GLOBAL_MANAGED_PROXY subnet doesn't exist, create one for each required region." type = string default = "default" } diff --git a/modules/serverless_negs/metadata.yaml b/modules/serverless_negs/metadata.yaml index 6617e3c5..68d6007f 100644 --- a/modules/serverless_negs/metadata.yaml +++ b/modules/serverless_negs/metadata.yaml @@ -52,6 +52,8 @@ spec: location: examples/https-redirect - name: internal-lb-cloud-run location: examples/internal-lb-cloud-run + - name: internal-lb-gce-mig + location: examples/internal-lb-gce-mig - name: lb-http-separate-frontend-and-backend location: examples/lb-http-separate-frontend-and-backend - name: mig-nat-http-lb diff --git a/test/integration/internal-lb-gce-mig/internal_lb_gce_mig_test.go b/test/integration/internal-lb-gce-mig/internal_lb_gce_mig_test.go new file mode 100644 index 00000000..adb5f840 --- /dev/null +++ b/test/integration/internal-lb-gce-mig/internal_lb_gce_mig_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal_lb_gce_mig + +import ( + "testing" + + "net/http" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestInternalLbGCEMIG(t *testing.T) { + bpt := tft.NewTFBlueprintTest(t) + + bpt.DefineVerify(func(assert *assert.Assertions) { + bpt.DefaultVerify(assert) + + cloudRunURIs := bpt.GetStringOutputList("external_cloudrun_uris") + + assertHttp := utils.NewAssertHTTP() + + for _, uri := range cloudRunURIs { + httpRequest, err := http.NewRequest("GET", uri, nil) + if err != nil { + t.Fatalf("Failed to create HTTP request for %s: %v", uri, err) + } + assertHttp.AssertResponse(t, httpRequest, http.StatusOK) + } + }) + + bpt.Test() +}