Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network Connectivity Center module #1219

Merged
merged 41 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
02707eb
Initial commit for NCC module
juliodiez Mar 6, 2023
e835730
Add router BGP peers
juliodiez Mar 6, 2023
25b1446
Simplify some naming
juliodiez Mar 6, 2023
6567164
Make optional some router config fields
juliodiez Mar 6, 2023
69493d8
Add README with first example
juliodiez Mar 6, 2023
0f4919a
Add image for site to VPC example
juliodiez Mar 6, 2023
71cb18f
Replace map key derived from resource attributes
juliodiez Mar 7, 2023
e7963eb
Set a unique name to spokes
juliodiez Mar 7, 2023
87107ba
Set a unique name to CRs linked to spokes
juliodiez Mar 7, 2023
449f5cb
Adapt example to use only allowed chars for resource names
juliodiez Mar 7, 2023
76972d5
Add example of site to two VPCs
juliodiez Mar 7, 2023
3e0a8c4
Add image for site to two VPCs example
juliodiez Mar 7, 2023
58c90fe
Add example of load-balanced router appliances
juliodiez Mar 7, 2023
9b5bc40
Add image for load-balanced router appliances example
juliodiez Mar 7, 2023
94f3a08
Add example of custom route advertisements
juliodiez Mar 7, 2023
ac224ad
Add tftest to README
juliodiez Mar 7, 2023
d9eaa59
Generated variable table via tfdoc
juliodiez Mar 7, 2023
5374c0e
Merge pull request #15 from GoogleCloudPlatform/master
juliodiez Mar 7, 2023
6eb82a2
Merge pull request #16 from juliodiez/master
juliodiez Mar 7, 2023
2f64fcd
Reimplement the module to manage only one spoke
juliodiez Mar 8, 2023
d5d7431
Make custom_advertise optional
juliodiez Mar 8, 2023
81121f4
data_transfer default to false
juliodiez Mar 8, 2023
0da0f33
Make keepalive optional
juliodiez Mar 8, 2023
1b4ba11
Make IPs for the CR interfaces optional
juliodiez Mar 8, 2023
34c6a6a
Make creation of the hub optional
juliodiez Mar 8, 2023
6196851
Output the name of the hub if created
juliodiez Mar 8, 2023
6253950
Update README for the new implementation
juliodiez Mar 8, 2023
93bb809
Rename module net-ncc -> ncc-spoke-ra
juliodiez Mar 8, 2023
96f35c5
Fix README variables to pass pytest
juliodiez Mar 8, 2023
5489162
Merge branch 'master' into ncc
ludoo Mar 8, 2023
e9312e4
var ras -> router_appliances
juliodiez Mar 9, 2023
b25ee97
Group vpc and subnet under vpc_config
juliodiez Mar 9, 2023
84d3b83
Group router information under router_config
juliodiez Mar 9, 2023
eef6a48
Make ip_interfaceX not optional
juliodiez Mar 9, 2023
7e6635f
Alphabetical order and better naming
juliodiez Mar 9, 2023
0cf254f
Update variable and output tables
juliodiez Mar 9, 2023
3e85175
Adapt README examples to the variables config
juliodiez Mar 9, 2023
7eb9fbf
Merge branch 'master' into ncc
juliodiez Mar 9, 2023
f82b528
Change semantics of custom_advertise
juliodiez Mar 9, 2023
d0f346f
Add resources created as outputs
juliodiez Mar 9, 2023
ff8f737
Merge branch 'master' into ncc
juliodiez Mar 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
134 changes: 134 additions & 0 deletions modules/ncc-spoke-ra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# NCC Spoke RA Module

This module allows management of NCC Spokes backed by Router Appliances. Network virtual appliances used as router appliances allow to connect an external network to Google Cloud by using a SD-WAN router or another appliance with BGP capabilities (_site-to-cloud_ connectivity). It is also possible to enable site-to-site data transfer, although this feature is not available in all regions, particularly not in EMEA.

The module manages a hub (optionally), a spoke, and the corresponding Cloud Router and BGP sessions to the router appliance(s).

## Examples

### Simple hub & spoke

```hcl
module "spoke-ra" {
source = "./fabric/modules/ncc-spoke-ra"
hub = { create = true, name = "ncc-hub" }
name = "spoke-ra"
project_id = "my-project"
asn = 65000
peer_asn = 65001
ras = [
{
vm = "projects/my-project/zones/europe-west1-b/instances/router-app"
ip = "10.0.0.3"
}
]
region = "europe-west1"
subnetwork = var.subnet.self_link
vpc = "my-vpc"
}
# tftest modules=1 resources=7
```

### Two spokes

```hcl
module "spoke-ra-a" {
source = "./fabric/modules/ncc-spoke-ra"
hub = { name = "ncc-hub" }
name = "spoke-ra-a"
project_id = "my-project"
asn = 65000
peer_asn = 65001
ras = [
{
vm = "projects/my-project/zones/europe-west1-b/instances/router-app-a"
ip = "10.0.0.3"
}
]
region = "europe-west1"
subnetwork = "projects/my-project/regions/europe-west1/subnetworks/subnet"
vpc = "my-vpc1"
}

module "spoke-ra-b" {
source = "./fabric/modules/ncc-spoke-ra"
hub = { name = "ncc-hub" }
name = "spoke-ra-b"
project_id = "my-project"
asn = 65000
peer_asn = 65002
ras = [
{
vm = "projects/my-project/zones/europe-west3-b/instances/router-app-b"
ip = "10.1.0.5"
}
]
region = "europe-west3"
subnetwork = "projects/my-project/regions/europe-west3/subnetworks/subnet"
vpc = "my-vpc2"
}
# tftest modules=2 resources=12
```

### Spoke with load-balanced router appliances

```hcl
module "spoke-ra" {
source = "./fabric/modules/ncc-spoke-ra"
hub = { name = "ncc-hub" }
name = "spoke-ra"
project_id = "my-project"
asn = 65000
custom_advertise = {
all_subnets = true
ip_ranges = {
"peered-vpc" = "10.10.0.0/24"
}
}
ip_intf1 = "10.0.0.14"
ip_intf2 = "10.0.0.15"
peer_asn = 65001
ras = [
{
vm = "projects/my-project/zones/europe-west1-b/instances/router-app-a"
ip = "10.0.0.3"
},
{
vm = "projects/my-project/zones/europe-west1-c/instances/router-app-b"
ip = "10.0.0.4"
}
]
region = "europe-west1"
subnetwork = var.subnet.self_link
vpc = "my-vpc"
}
# tftest modules=1 resources=8
```
<!-- BEGIN TFDOC -->

## Variables

| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [asn](variables.tf#L17) | Autonomous System Number for the CR. All spokes in a hub should use the same ASN. | <code>number</code> | ✓ | |
| [hub](variables.tf#L37) | The name of the NCC hub to create or use. | <code title="object&#40;&#123;&#10; create &#61; optional&#40;bool, false&#41;&#10; name &#61; string&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [name](variables.tf#L64) | The name of the NCC spoke. | <code>string</code> | ✓ | |
| [peer_asn](variables.tf#L69) | Peer Autonomous System Number used by the router appliances. | <code>number</code> | ✓ | |
| [project_id](variables.tf#L74) | The ID of the project where the NCC hub & spokes will be created. | <code>string</code> | ✓ | |
| [ras](variables.tf#L79) | List of router appliances this spoke is associated with. | <code title="list&#40;object&#40;&#123;&#10; vm &#61; string &#35; URI&#10; ip &#61; string&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | ✓ | |
| [region](variables.tf#L87) | Region where the spoke is located. | <code>string</code> | ✓ | |
| [subnetwork](variables.tf#L92) | The URI of the subnetwork that CR interfaces belong to. | <code>string</code> | ✓ | |
| [vpc](variables.tf#L97) | A reference to the network to which the CR belongs. | <code>string</code> | ✓ | |
| [custom_advertise](variables.tf#L22) | IP ranges to advertise if not using default route advertisement (subnet ranges). | <code title="object&#40;&#123;&#10; all_subnets &#61; bool&#10; ip_ranges &#61; map&#40;string&#41; &#35; map of descriptions and address ranges&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [data_transfer](variables.tf#L31) | Site-to-site data transfer feature, available only in some regions. | <code>bool</code> | | <code>false</code> |
| [ip_intf1](variables.tf#L46) | IP address for the CR interface 1. It must belong to the primary range of the subnet. If you don't specify a value Google will try to find a free address. | <code>string</code> | | <code>null</code> |
| [ip_intf2](variables.tf#L52) | IP address for the CR interface 2. It must belong to the primary range of the subnet. If you don't specify a value Google will try to find a free address. | <code>string</code> | | <code>null</code> |
| [keepalive](variables.tf#L58) | The interval in seconds between BGP keepalive messages that are sent to the peer. | <code>number</code> | | <code>null</code> |

## Outputs

| name | description | sensitive |
|---|---|:---:|
| [hub_name](outputs.tf#L17) | NCC hub name (only if auto-created). | |

<!-- END TFDOC -->
121 changes: 121 additions & 0 deletions modules/ncc-spoke-ra/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Copyright 2023 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.
*/

locals {
spoke_vms = [
for ras in var.ras : {
ip = ras.ip
vm = ras.vm
vm_name = element(split("/", ras.vm), length(split("/", ras.vm)) - 1)
}
]
}

resource "google_network_connectivity_hub" "hub" {
count = var.hub.create ? 1 : 0
project = var.project_id
name = var.hub.name
description = var.hub.description
}

resource "google_network_connectivity_spoke" "spoke-ra" {
project = var.project_id
hub = try(google_network_connectivity_hub.hub[0].name, var.hub.name)
location = var.region
name = var.name
linked_router_appliance_instances {
dynamic "instances" {
for_each = var.ras
content {
virtual_machine = instances.value["vm"]
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
ip_address = instances.value["ip"]
}
}
site_to_site_data_transfer = var.data_transfer
}
}

resource "google_compute_router" "cr" {
ludoo marked this conversation as resolved.
Show resolved Hide resolved
project = var.project_id
name = "${var.name}-cr"
network = var.vpc
region = var.region
bgp {
advertise_mode = (
var.custom_advertise != null ? "CUSTOM" : "DEFAULT"
)
advertised_groups = (
try(var.custom_advertise.all_subnets, false)
? ["ALL_SUBNETS"] : []
)
dynamic "advertised_ip_ranges" {
for_each = try(var.custom_advertise.ip_ranges, {})
content {
description = advertised_ip_ranges.key
range = advertised_ip_ranges.value
}
}
asn = var.asn
keepalive_interval = try(var.keepalive, null)
}
}

resource "google_compute_router_interface" "intf1" {
project = var.project_id
name = "intf1"
router = google_compute_router.cr.name
region = var.region
subnetwork = var.subnetwork
private_ip_address = var.ip_intf1
}

resource "google_compute_router_interface" "intf2" {
project = var.project_id
name = "intf2"
router = google_compute_router.cr.name
region = var.region
subnetwork = var.subnetwork
private_ip_address = var.ip_intf2
redundant_interface = google_compute_router_interface.intf1.name
}

resource "google_compute_router_peer" "peer1" {
for_each = {
for idx, entry in local.spoke_vms : idx => entry
}
project = var.project_id
name = "peer1-${each.value.vm_name}"
router = google_compute_router.cr.name
region = var.region
interface = google_compute_router_interface.intf1.name
peer_asn = var.peer_asn
peer_ip_address = each.value.ip
router_appliance_instance = each.value.vm
}

resource "google_compute_router_peer" "peer2" {
for_each = {
for idx, entry in local.spoke_vms : idx => entry
}
project = var.project_id
name = "peer2-${each.value.vm_name}"
router = google_compute_router.cr.name
region = var.region
interface = google_compute_router_interface.intf2.name
peer_asn = var.peer_asn
peer_ip_address = each.value.ip
router_appliance_instance = each.value.vm
}
20 changes: 20 additions & 0 deletions modules/ncc-spoke-ra/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2023 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.
*/

juliodiez marked this conversation as resolved.
Show resolved Hide resolved
output "hub_name" {
description = "NCC hub name (only if auto-created)."
value = one(google_network_connectivity_hub.hub[*].name)
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
}
100 changes: 100 additions & 0 deletions modules/ncc-spoke-ra/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright 2023 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 "asn" {
description = "Autonomous System Number for the CR. All spokes in a hub should use the same ASN."
type = number
}

variable "custom_advertise" {
description = "IP ranges to advertise if not using default route advertisement (subnet ranges)."
type = object({
all_subnets = bool
ip_ranges = map(string) # map of descriptions and address ranges
})
default = null
}

variable "data_transfer" {
description = "Site-to-site data transfer feature, available only in some regions."
type = bool
default = false
}

variable "hub" {
description = "The name of the NCC hub to create or use."
type = object({
create = optional(bool, false)
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
name = string
description = optional(string)
})
}

variable "ip_intf1" {
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
description = "IP address for the CR interface 1. It must belong to the primary range of the subnet. If you don't specify a value Google will try to find a free address."
type = string
default = null
}

variable "ip_intf2" {
description = "IP address for the CR interface 2. It must belong to the primary range of the subnet. If you don't specify a value Google will try to find a free address."
type = string
default = null
}

variable "keepalive" {
description = "The interval in seconds between BGP keepalive messages that are sent to the peer."
type = number
default = null
}

variable "name" {
description = "The name of the NCC spoke."
type = string
}

variable "peer_asn" {
description = "Peer Autonomous System Number used by the router appliances."
type = number
}

variable "project_id" {
description = "The ID of the project where the NCC hub & spokes will be created."
type = string
}

variable "ras" {
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
description = "List of router appliances this spoke is associated with."
type = list(object({
vm = string # URI
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
ip = string
}))
}

variable "region" {
description = "Region where the spoke is located."
type = string
}

variable "subnetwork" {
juliodiez marked this conversation as resolved.
Show resolved Hide resolved
description = "The URI of the subnetwork that CR interfaces belong to."
type = string
}

variable "vpc" {
description = "A reference to the network to which the CR belongs."
type = string
}