diff --git a/modules/net-lb-int/README.md b/modules/net-lb-int/README.md
index 24308dab04..49e393783b 100644
--- a/modules/net-lb-int/README.md
+++ b/modules/net-lb-int/README.md
@@ -10,6 +10,7 @@ This module allows managing a GCE Internal Load Balancer and integrates the forw
- [Multiple forwarding rules](#multiple-forwarding-rules)
- [Dual stack (IPv4 and IPv6)](#dual-stack-ipv4-and-ipv6)
- [PSC service attachments](#psc-service-attachments)
+ - [Zonal affinity traffic policy](#zonal-affinity-traffic-policy)
- [Regional health check](#regional-health-check)
- [End to end example](#end-to-end-example)
- [Context](#context)
@@ -282,6 +283,45 @@ module "ilb" {
# tftest modules=1 resources=7
```
+### Zonal affinity traffic policy
+
+The `backend_service_config.network_pass_through_lb_traffic_policy` block allows tuning the backend service behavior for network passthrough load balancers, including zonal affinity spillover settings.
+
+```hcl
+module "ilb" {
+ source = "./fabric/modules/net-lb-int"
+ project_id = var.project_id
+ region = "europe-west1"
+ name = "ilb-test"
+ service_label = "ilb-test"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }
+ backend_service_config = {
+ network_pass_through_lb_traffic_policy = {
+ zonal_affinity = {
+ spillover = "ZONAL_AFFINITY_SPILL_CROSS_ZONE"
+ spillover_ratio = 0.5
+ }
+ }
+ }
+ group_configs = {
+ my-group = {
+ zone = "europe-west1-b"
+ instances = [
+ "instance-1-self-link",
+ "instance-2-self-link"
+ ]
+ }
+ }
+ backends = [{
+ group = module.ilb.groups.my-group.self_link
+ }]
+}
+# tftest modules=1 resources=4
+```
+
### Regional health check
The `is_regional` flag in the `health_check_config` block allows creating a regional health check instead of a global one.
@@ -459,21 +499,21 @@ One other issue is a `Provider produced inconsistent final plan` error which is
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L208) | Name used for all resources. | string | ✓ | |
-| [project_id](variables.tf#L213) | Project id where resources will be created. | string | ✓ | |
-| [region](variables.tf#L218) | GCP region. | string | ✓ | |
-| [vpc_config](variables.tf#L244) | VPC-level configuration. | object({…}) | ✓ | |
+| [name](variables.tf#L235) | Name used for all resources. | string | ✓ | |
+| [project_id](variables.tf#L240) | Project id where resources will be created. | string | ✓ | |
+| [region](variables.tf#L245) | GCP region. | string | ✓ | |
+| [vpc_config](variables.tf#L271) | VPC-level configuration. | object({…}) | ✓ | |
| [backend_service_config](variables.tf#L17) | Backend service level configuration. | object({…}) | | {} |
-| [backends](variables.tf#L58) | Load balancer backends. | list(object({…})) | | [] |
-| [context](variables.tf#L69) | Context-specific interpolations. | object({…}) | | {} |
-| [description](variables.tf#L82) | Optional description used for resources. | string | | "Terraform managed." |
-| [forwarding_rules_config](variables.tf#L88) | The optional forwarding rules configuration. | map(object({…})) | | {…} |
-| [group_configs](variables.tf#L104) | Optional unmanaged groups to create. Can be referenced in backends via outputs. | map(object({…})) | | {} |
-| [health_check](variables.tf#L117) | Name of existing health check to use, disables auto-created health check. Also set `health_check_config = null` when cross-referencing an health check from another load balancer module to avoid a Terraform error. | string | | null |
-| [health_check_config](variables.tf#L123) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({…}) | | {…} |
-| [labels](variables.tf#L202) | Labels set on resources. | map(string) | | {} |
-| [service_attachments](variables.tf#L223) | PSC service attachments, keyed by forwarding rule. | map(object({…})) | | null |
-| [service_label](variables.tf#L238) | Optional prefix of the fully qualified forwarding rule name. | string | | null |
+| [backends](variables.tf#L85) | Load balancer backends. | list(object({…})) | | [] |
+| [context](variables.tf#L96) | Context-specific interpolations. | object({…}) | | {} |
+| [description](variables.tf#L109) | Optional description used for resources. | string | | "Terraform managed." |
+| [forwarding_rules_config](variables.tf#L115) | The optional forwarding rules configuration. | map(object({…})) | | {…} |
+| [group_configs](variables.tf#L131) | Optional unmanaged groups to create. Can be referenced in backends via outputs. | map(object({…})) | | {} |
+| [health_check](variables.tf#L144) | Name of existing health check to use, disables auto-created health check. Also set `health_check_config = null` when cross-referencing an health check from another load balancer module to avoid a Terraform error. | string | | null |
+| [health_check_config](variables.tf#L150) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({…}) | | {…} |
+| [labels](variables.tf#L229) | Labels set on resources. | map(string) | | {} |
+| [service_attachments](variables.tf#L250) | PSC service attachments, keyed by forwarding rule. | map(object({…})) | | null |
+| [service_label](variables.tf#L265) | Optional prefix of the fully qualified forwarding rule name. | string | | null |
## Outputs
diff --git a/modules/net-lb-int/main.tf b/modules/net-lb-int/main.tf
index 273b5cc957..368cccd87b 100644
--- a/modules/net-lb-int/main.tf
+++ b/modules/net-lb-int/main.tf
@@ -21,6 +21,7 @@ locals {
)
bs_conntrack = var.backend_service_config.connection_tracking
bs_failover = var.backend_service_config.failover_config
+ bs_nptlb = var.backend_service_config.network_pass_through_lb_traffic_policy
forwarding_rule_names = {
for k, v in var.forwarding_rules_config :
k => k == "" ? var.name : "${var.name}-${k}"
@@ -150,6 +151,18 @@ resource "google_compute_region_backend_service" "default" {
}
}
+ dynamic "network_pass_through_lb_traffic_policy" {
+ for_each = local.bs_nptlb != null ? [""] : []
+ content {
+ dynamic "zonal_affinity" {
+ for_each = local.bs_nptlb.zonal_affinity != null ? [""] : []
+ content {
+ spillover = local.bs_nptlb.zonal_affinity.spillover
+ spillover_ratio = local.bs_nptlb.zonal_affinity.spillover_ratio
+ }
+ }
+ }
+ }
}
resource "google_compute_service_attachment" "default" {
diff --git a/modules/net-lb-int/variables.tf b/modules/net-lb-int/variables.tf
index 2f1ce22f71..2583bf8a67 100644
--- a/modules/net-lb-int/variables.tf
+++ b/modules/net-lb-int/variables.tf
@@ -35,6 +35,12 @@ variable "backend_service_config" {
optional_mode = optional(string)
optional_fields = optional(list(string))
}))
+ network_pass_through_lb_traffic_policy = optional(object({
+ zonal_affinity = object({
+ spillover = optional(string, "ZONAL_AFFINITY_DISABLED")
+ spillover_ratio = optional(number)
+ })
+ }))
name = optional(string)
description = optional(string, "Terraform managed.")
protocol = optional(string, "UNSPECIFIED")
@@ -53,6 +59,27 @@ variable "backend_service_config" {
)
error_message = "Invalid session affinity value."
}
+ validation {
+ condition = (
+ try(var.backend_service_config.network_pass_through_lb_traffic_policy, null) == null
+ || contains(
+ ["ZONAL_AFFINITY_DISABLED", "ZONAL_AFFINITY_SPILL_CROSS_ZONE", "ZONAL_AFFINITY_STAY_WITHIN_ZONE"],
+ coalesce(var.backend_service_config.network_pass_through_lb_traffic_policy.zonal_affinity.spillover, "ZONAL_AFFINITY_DISABLED")
+ )
+ )
+ error_message = "network_pass_through_lb_traffic_policy.zonal_affinity.spillover must be one of ZONAL_AFFINITY_DISABLED, ZONAL_AFFINITY_SPILL_CROSS_ZONE, or ZONAL_AFFINITY_STAY_WITHIN_ZONE."
+ }
+ validation {
+ condition = (
+ try(var.backend_service_config.network_pass_through_lb_traffic_policy, null) == null
+ || try(var.backend_service_config.network_pass_through_lb_traffic_policy.zonal_affinity.spillover_ratio, null) == null
+ || (
+ var.backend_service_config.network_pass_through_lb_traffic_policy.zonal_affinity.spillover_ratio >= 0 &&
+ var.backend_service_config.network_pass_through_lb_traffic_policy.zonal_affinity.spillover_ratio <= 1
+ )
+ )
+ error_message = "network_pass_through_lb_traffic_policy.zonal_affinity.spillover_ratio must be a number between 0 and 1."
+ }
}
variable "backends" {
diff --git a/tests/modules/net_lb_int/tftest.yaml b/tests/modules/net_lb_int/tftest.yaml
index a691278c5c..2cbaa775aa 100644
--- a/tests/modules/net_lb_int/tftest.yaml
+++ b/tests/modules/net_lb_int/tftest.yaml
@@ -24,3 +24,4 @@ tests:
health-checks-https:
health-checks-ssl:
health-checks-tcp:
+ zonal-affinity:
diff --git a/tests/modules/net_lb_int/zonal-affinity.tfvars b/tests/modules/net_lb_int/zonal-affinity.tfvars
new file mode 100644
index 0000000000..788935710a
--- /dev/null
+++ b/tests/modules/net_lb_int/zonal-affinity.tfvars
@@ -0,0 +1,32 @@
+# Copyright 2026 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.
+
+project_id = "my-project"
+region = "europe-west1"
+name = "ilb-test"
+vpc_config = {
+ network = "default"
+ subnetwork = "default"
+}
+backends = [{
+ group = "foo"
+}]
+backend_service_config = {
+ network_pass_through_lb_traffic_policy = {
+ zonal_affinity = {
+ spillover = "ZONAL_AFFINITY_SPILL_CROSS_ZONE"
+ spillover_ratio = 0.5
+ }
+ }
+}
diff --git a/tests/modules/net_lb_int/zonal-affinity.yaml b/tests/modules/net_lb_int/zonal-affinity.yaml
new file mode 100644
index 0000000000..9fb607c297
--- /dev/null
+++ b/tests/modules/net_lb_int/zonal-affinity.yaml
@@ -0,0 +1,23 @@
+# Copyright 2026 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.
+
+values:
+ google_compute_region_backend_service.default:
+ network_pass_through_lb_traffic_policy:
+ - zonal_affinity:
+ - spillover: ZONAL_AFFINITY_SPILL_CROSS_ZONE
+ spillover_ratio: 0.5
+
+counts:
+ google_compute_region_backend_service: 1