diff --git a/modules/cloud-config-container/simple-nva/README.md b/modules/cloud-config-container/simple-nva/README.md index f70842e8..e0800d17 100644 --- a/modules/cloud-config-container/simple-nva/README.md +++ b/modules/cloud-config-container/simple-nva/README.md @@ -62,16 +62,88 @@ module "vm" { } # tftest modules=1 resources=1 ``` + +### Example with advanced routing capabilities + +Find below a sample terraform example for bootstrapping a simple NVA powered by [COS](https://cloud.google.com/container-optimized-os/docs) and running [FRRouting](https://frrouting.org/) container. +Please find below a sample frr.conf file based on the documentation available [here](https://docs.frrouting.org/en/latest/basic.html) for hosting a BGP service with ASN 65001 on FRR container establishing a BGP session with a remote neighbor with IP address 10.128.0.2 and ASN 65002. + +``` +# tftest-file id=frr_conf path=./frr.conf +# Example frr.conmf file + +log syslog informational +no ipv6 forwarding +router bgp 65001 + neighbor 10.128.0.2 remote-as 65002 +line vty +``` + +Following code assumes a file in the same folder named frr.conf exists. + +```hcl +locals { + network_interfaces = [ + { + addresses = null + name = "dev" + nat = false + network = "dev_vpc_self_link" + routes = ["10.128.0.0/9"] + subnetwork = "dev_vpc_nva_subnet_self_link" + enable_masquerading = true + non_masq_cidrs = ["10.0.0.0/8"] + }, + { + addresses = null + name = "prod" + nat = false + network = "prod_vpc_self_link" + routes = ["10.0.0.0/9"] + subnetwork = "prod_vpc_nva_subnet_self_link" + } + ] +} + +module "cos-nva" { + source = "./fabric/modules/cloud-config-container/simple-nva" + enable_health_checks = true + network_interfaces = local.network_interfaces + frr_config = { config_file = "./frr.conf", daemons_enabled = ["bgpd"] } + optional_run_cmds = ["ls -l"] +} + +module "vm" { + source = "./fabric/modules/compute-vm" + project_id = "my-project" + zone = "europe-west8-b" + name = "cos-nva" + network_interfaces = local.network_interfaces + metadata = { + user-data = module.cos-nva.cloud_config + google-logging-enabled = true + } + boot_disk = { + image = "projects/cos-cloud/global/images/family/cos-stable" + type = "pd-ssd" + size = 10 + } + tags = ["nva", "ssh"] +} +# tftest modules=1 resources=1 files=frr_conf +``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [network_interfaces](variables.tf#L39) | Network interfaces configuration. | list(object({…})) | ✓ | | +| [network_interfaces](variables.tf#L75) | Network interfaces configuration. | list(object({…})) | ✓ | | | [cloud_config](variables.tf#L17) | Cloud config template path. If null default will be used. | string | | null | | [enable_health_checks](variables.tf#L23) | Configures routing to enable responses to health check probes. | bool | | false | | [files](variables.tf#L29) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} | +| [frr_config](variables.tf#L39) | FRR configuration for container running on the NVA. | object({…}) | | null | +| [optional_run_cmds](variables.tf#L84) | Optional Cloud Init run commands to execute. | list(string) | | [] | ## Outputs diff --git a/modules/cloud-config-container/simple-nva/cloud-config.yaml b/modules/cloud-config-container/simple-nva/cloud-config.yaml index f1d71e82..f44cd08e 100644 --- a/modules/cloud-config-container/simple-nva/cloud-config.yaml +++ b/modules/cloud-config-container/simple-nva/cloud-config.yaml @@ -1,6 +1,6 @@ #cloud-config -# Copyright 2022 Google LLC +# 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. @@ -22,6 +22,7 @@ write_files: content: | ${indent(6, data.content)} %{ endfor } + - path: /etc/systemd/system/routing.service permissions: 0644 owner: root @@ -34,6 +35,7 @@ write_files: Wants=network-online.target [Service] ExecStart=/bin/sh -c "/var/run/nva/start-routing.sh" + - path: /var/run/nva/start-routing.sh permissions: 0744 owner: root @@ -43,6 +45,12 @@ write_files: %{ if enable_health_checks ~} /var/run/nva/policy_based_routing.sh ${interface.name} %{ endif ~} +%{ if interface.enable_masquerading ~} +%{ for cidr in interface.non_masq_cidrs ~} + iptables -t nat -A POSTROUTING -o ${interface.name} -d ${cidr} -j ACCEPT +%{ endfor ~} + iptables -t nat -A POSTROUTING -o ${interface.name} -j MASQUERADE +%{ endif ~} %{ for route in interface.routes ~} ip route add ${route} via `curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/${interface.number}/gateway -H "Metadata-Flavor:Google"` dev ${interface.name} %{ endfor ~} @@ -55,4 +63,6 @@ runcmd: - systemctl daemon-reload - systemctl enable routing - systemctl start routing - +%{ for cmd in optional_run_cmds ~} + - ${cmd} +%{ endfor ~} diff --git a/modules/cloud-config-container/simple-nva/files/frr/daemons b/modules/cloud-config-container/simple-nva/files/frr/daemons new file mode 100644 index 00000000..0a388df0 --- /dev/null +++ b/modules/cloud-config-container/simple-nva/files/frr/daemons @@ -0,0 +1,65 @@ +# 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. + +zebra=${zebra_enabled} +bgpd=${bgpd_enabled} +ospfd=${ospfd_enabled} +ospf6d=${ospf6d_enabled} +ripd=${ripd_enabled} +ripngd=${ripngd_enabled} +isisd=${isisd_enabled} +pimd=${pimd_enabled} +ldpd=${ldpd_enabled} +nhrpd=${nhrpd_enabled} +eigrpd=${eigrpd_enabled} +babeld=${babeld_enabled} +sharpd=${sharpd_enabled} +staticd=${staticd_enabled} +pbrd=${pbrd_enabled} +bfdd=${bfdd_enabled} +fabricd=${fabricd_enabled} + +# If this option is set the /etc/init.d/frr script automatically loads +# the config via "vtysh -b" when the servers are started. +# Check /etc/pam.d/frr if you intend to use "vtysh"! + +vtysh_enable=yes +zebra_options=" -A 127.0.0.1 -s 90000000" +bgpd_options=" -A 127.0.0.1" +ospfd_options=" --daemon -A 127.0.0.1" +ospf6d_options=" --daemon -A ::1" +ripd_options=" --daemon -A 127.0.0.1" +ripngd_options=" --daemon -A ::1" +isisd_options=" --daemon -A 127.0.0.1" +pimd_options=" --daemon -A 127.0.0.1" +ldpd_options=" --daemon -A 127.0.0.1" +nhrpd_options=" --daemon -A 127.0.0.1" +eigrpd_options=" --daemon -A 127.0.0.1" +babeld_options=" --daemon -A 127.0.0.1" +sharpd_options=" --daemon -A 127.0.0.1" +staticd_options=" --daemon -A 127.0.0.1" +pbrd_options=" --daemon -A 127.0.0.1" +bfdd_options=" --daemon -A 127.0.0.1" +fabricd_options=" --daemon -A 127.0.0.1" + +#MAX_FDS=1024 +# The list of daemons to watch is automatically generated by the init script. +#watchfrr_options="" + +# for debugging purposes, you can specify a "wrap" command to start instead +# of starting the daemon directly, e.g. to use valgrind on ospfd: +# ospfd_wrap="/usr/bin/valgrind" +# or you can use "all_wrap" for all daemons, e.g. to use perf record: +# all_wrap="/usr/bin/perf record --call-graph -" +# the normal daemon command is added to this at the end. diff --git a/modules/cloud-config-container/simple-nva/files/frr/frr.service b/modules/cloud-config-container/simple-nva/files/frr/frr.service new file mode 100644 index 00000000..a560602e --- /dev/null +++ b/modules/cloud-config-container/simple-nva/files/frr/frr.service @@ -0,0 +1,27 @@ +# 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. + +[Unit] +Description=Start FRR container +After=gcr-online.target docker.socket +Wants=gcr-online.target docker.socket docker-events-collector.service +[Service] +Environment="HOME=/home/frr" +ExecStart=/usr/bin/docker run --rm --name=frr \ +--privileged \ +--network host \ +-v /etc/frr:/etc/frr \ +frrouting/frr +ExecStop=/usr/bin/docker stop frr +ExecStopPost=/usr/bin/docker rm frr diff --git a/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh b/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh index a1c69822..16943825 100644 --- a/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh +++ b/modules/cloud-config-container/simple-nva/files/ipprefix_by_netmask.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2022 Google LLC +# 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. diff --git a/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh b/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh index 951396d3..49f38288 100644 --- a/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh +++ b/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2022 Google LLC +# 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. diff --git a/modules/cloud-config-container/simple-nva/main.tf b/modules/cloud-config-container/simple-nva/main.tf index 4ff0afe2..110983bf 100644 --- a/modules/cloud-config-container/simple-nva/main.tf +++ b/modules/cloud-config-container/simple-nva/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -15,42 +15,109 @@ */ locals { - cloud_config = templatefile(local.template, merge({ - files = local.files - enable_health_checks = var.enable_health_checks - network_interfaces = local.network_interfaces - })) + _files = merge( + { + "/var/run/nva/ipprefix_by_netmask.sh" = { + content = file("${path.module}/files/ipprefix_by_netmask.sh") + owner = "root" + permissions = "0744" + } + "/var/run/nva/policy_based_routing.sh" = { + content = file("${path.module}/files/policy_based_routing.sh") + owner = "root" + permissions = "0744" + } + }, { + for path, attrs in var.files : path => { + content = attrs.content, + owner = attrs.owner, + permissions = attrs.permissions + } + }, + try(var.frr_config != null, false) ? { + "/etc/frr/daemons" = { + content = templatefile("${path.module}/files/frr/daemons", local._frr_daemons_enabled) + owner = "root" + permissions = "0744" + } + "/etc/frr/frr.conf" = { + content = file(var.frr_config.config_file) + owner = "root" + permissions = "0744" + } + "/etc/systemd/system/frr.service" = { + content = file("${path.module}/files/frr/frr.service") + owner = "root" + permissions = "0644" + } + "/var/lib/docker/daemon.json" = { + content = < { - content = attrs.content, - owner = attrs.owner, - permissions = attrs.permissions - } - }) + _frr_daemons = [ + "zebra", + "bgpd", + "ospfd", + "ospf6d", + "ripd", + "ripngd", + "isisd", + "pimd", + "ldpd", + "nhrpd", + "eigrpd", + "babeld", + "sharpd", + "staticd", + "pbrd", + "bfdd", + "fabricd" + ] - network_interfaces = [ + _frr_daemons_enabled = try( + { + for daemon in local._frr_daemons : + "${daemon}_enabled" => contains(var.frr_config.daemons_enabled, daemon) ? "yes" : "no" + }, {}) + + _network_interfaces = [ for index, interface in var.network_interfaces : { - name = "eth${index}" - number = index - routes = interface.routes + name = "eth${index}" + number = index + routes = interface.routes + enable_masquerading = interface.enable_masquerading != null ? interface.enable_masquerading : false + non_masq_cidrs = interface.non_masq_cidrs != null ? interface.non_masq_cidrs : [] } ] - template = ( + _optional_run_cmds = ( + try(var.frr_config != null, false) + ? concat(["systemctl start frr"], var.optional_run_cmds) + : var.optional_run_cmds + ) + + _template = ( var.cloud_config == null ? "${path.module}/cloud-config.yaml" : var.cloud_config ) + + cloud_config = templatefile(local._template, { + enable_health_checks = var.enable_health_checks + files = local._files + network_interfaces = local._network_interfaces + optional_run_cmds = local._optional_run_cmds + }) } diff --git a/modules/cloud-config-container/simple-nva/outputs.tf b/modules/cloud-config-container/simple-nva/outputs.tf index 7d8d4165..54942c1a 100644 --- a/modules/cloud-config-container/simple-nva/outputs.tf +++ b/modules/cloud-config-container/simple-nva/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. diff --git a/modules/cloud-config-container/simple-nva/variables.tf b/modules/cloud-config-container/simple-nva/variables.tf index 39d96d91..84f62f69 100644 --- a/modules/cloud-config-container/simple-nva/variables.tf +++ b/modules/cloud-config-container/simple-nva/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -36,9 +36,53 @@ variable "files" { default = {} } +variable "frr_config" { + description = "FRR configuration for container running on the NVA." + type = object({ + daemons_enabled = optional(list(string)) + config_file = string + }) + default = null + validation { + condition = try(alltrue([ + for daemon in var.frr_config.daemons_enabled : contains([ + "zebra", + "bgpd", + "ospfd", + "ospf6d", + "ripd", + "ripngd", + "isisd", + "pimd", + "ldpd", + "nhrpd", + "eigrpd", + "babeld", + "sharpd", + "staticd", + "pbrd", + "bfdd", + "fabricd" + ], daemon) + ]), true) + error_message = <