Files
CloudRouterAdvanced/terraform/main.tf
T
ayurishchevandClaude Sonnet 5 5979a9a58b Add adaptive router VM/interface scaling and local delivery integrity tests
Terraform now provisions router_count IaaS Router VMs (default 2, no
longer hardcoded to router1/router2), each with 1 public +
private_interface_count isolated private interfaces (no shared LAN or
VRRP between routers). Both counts scale via Terraform variables and
TF_VAR_* environment variables. The post-install script became a
Terraform template that matches interfaces to their expected subnet by
CIDR instead of a fragile "first private IP" heuristic.

Added an offline pytest suite (terraform/tests/) that checks the
delivery's internal consistency and runs real terraform init/validate
against the actual vkcs provider schema via a project-local filesystem
mirror (provider binary fetched from its GitHub releases, bypassing the
region-blocked HashiCorp registry) - no cloud credentials or API calls
involved. terraform/versions.tf now declares the previously-missing
required_providers block.

Ansible (inventory.ini, base/frr_router/keepalived roles) still assumes
the old 2-router/2-NIC/VRRP topology and is not yet adapted - documented
as a follow-up, not addressed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hXR2ftXZZhJ4Y3XuSoR8r
2026-09-03 16:03:12 +03:00

288 lines
8.2 KiB
Terraform

data "vkcs_images_image" "ubuntu24" {
visibility = "public"
most_recent = true
properties = {
mcs_os_distro = "ubuntu"
mcs_os_version = "24.04"
}
}
data "vkcs_networking_network" "extnet" {
name = "internet"
sdn = "sprut"
}
# LAN Network
resource "vkcs_networking_network" "lan_net" {
name = "router-lan-net"
sdn = "sprut"
admin_state_up = true
}
resource "vkcs_networking_subnet" "lan_subnet" {
network_id = vkcs_networking_network.lan_net.id
name = "router-lan-subnet"
cidr = "10.200.10.0/24"
gateway_ip = "10.200.10.1"
dns_nameservers = ["8.8.8.8", "1.1.1.1"]
sdn = "sprut"
allocation_pool {
start = "10.200.10.100"
end = "10.200.10.200"
}
}
# Security Groups
resource "vkcs_networking_secgroup" "router_sg" {
name = "router-sg"
sdn = "sprut"
}
resource "vkcs_networking_secgroup" "private_sg" {
name = "private-sg"
sdn = "sprut"
}
# Private SG rules
resource "vkcs_networking_secgroup_rule" "from_rfc_net192_in" {
direction = "ingress"
remote_ip_prefix = "192.168.0.0/16"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "from_rfc_net172_in" {
direction = "ingress"
remote_ip_prefix = "172.16.0.0/12"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "from_rfc_net10_in" {
direction = "ingress"
remote_ip_prefix = "10.0.0.0/8"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
# Router SG rules
resource "vkcs_networking_secgroup_rule" "router_ssh" {
direction = "ingress"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = vkcs_networking_secgroup.router_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "router_icmp" {
direction = "ingress"
protocol = "icmp"
remote_ip_prefix = "0.0.0.0/0"
security_group_id = vkcs_networking_secgroup.router_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "router_ipsec_ike" {
direction = "ingress"
protocol = "udp"
port_range_min = 500
port_range_max = 500
remote_ip_prefix = "0.0.0.0/0"
security_group_id = vkcs_networking_secgroup.router_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "router_ipsec_nat_t" {
direction = "ingress"
protocol = "udp"
port_range_min = 4500
port_range_max = 4500
remote_ip_prefix = "0.0.0.0/0"
security_group_id = vkcs_networking_secgroup.router_sg.id
sdn = "sprut"
}
# Per-router private interface subnets: each router gets `private_roles` isolated
# micro-subnets (no shared LAN, no VRRP), carved out of private_supernet.
locals {
private_roles = [for i in range(var.private_interface_count) : "priv${i + 1}"]
router_private_subnets = {
for pair in setproduct(range(var.router_count), local.private_roles) :
"router${pair[0] + 1}-${pair[1]}" => {
router_index = pair[0]
role = pair[1]
cidr = cidrsubnet(
var.private_supernet,
var.private_subnet_prefix_length - tonumber(split("/", var.private_supernet)[1]),
pair[0] * length(local.private_roles) + index(local.private_roles, pair[1])
)
}
}
}
resource "vkcs_networking_network" "router_priv_net" {
for_each = local.router_private_subnets
name = "router-${each.key}-net"
sdn = "sprut"
admin_state_up = true
}
resource "vkcs_networking_subnet" "router_priv_subnet" {
for_each = local.router_private_subnets
network_id = vkcs_networking_network.router_priv_net[each.key].id
name = "router-${each.key}-subnet"
cidr = each.value.cidr
gateway_ip = cidrhost(each.value.cidr, 1)
sdn = "sprut"
}
resource "vkcs_networking_port" "router_priv_port" {
for_each = local.router_private_subnets
name = "router-${each.key}-port"
network_id = vkcs_networking_network.router_priv_net[each.key].id
admin_state_up = true
port_security_enabled = false
full_security_groups_control = true
security_group_ids = []
sdn = "sprut"
fixed_ip {
subnet_id = vkcs_networking_subnet.router_priv_subnet[each.key].id
ip_address = cidrhost(each.value.cidr, 2)
}
}
resource "vkcs_compute_instance" "router" {
count = var.router_count
name = "router${count.index + 1}"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = element(var.router_availability_zones, count.index % length(var.router_availability_zones))
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.router_sg.id,
"d479b4d7-55b3-4ff1-bf8d-24d826a38f11"
]
config_drive = true
# Configure persistent networking using script: WAN via heuristic public-IP
# detection, private interfaces matched by their expected CIDR.
user_data = templatefile("${path.module}/scripts/network-init.sh.tpl", {
private_interfaces = [
for role in local.private_roles : {
name = "eth${index(local.private_roles, role) + 1}"
cidr = local.router_private_subnets["router${count.index + 1}-${role}"].cidr
}
]
})
# WAN: dynamically created port
network {
uuid = data.vkcs_networking_network.extnet.id
}
# Private: one pre-created isolated port per role
dynamic "network" {
for_each = local.private_roles
content {
port = vkcs_networking_port.router_priv_port["router${count.index + 1}-${network.value}"].id
}
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
resource "vkcs_compute_instance" "priv_srv_01" {
name = "Priv-SRV-01"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "ME1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
"d479b4d7-55b3-4ff1-bf8d-24d826a38f11"
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
resource "vkcs_compute_instance" "priv_srv_02" {
name = "Priv-SRV-02"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "ME1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
"d479b4d7-55b3-4ff1-bf8d-24d826a38f11"
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
resource "vkcs_compute_instance" "priv_srv_03" {
name = "Priv-SRV-03"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "MS1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
"d479b4d7-55b3-4ff1-bf8d-24d826a38f11"
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}