Confirmed working against a real VK Cloud PROD deployment (3 routers, 19 resources, apply succeeded end to end). Fixes found along the way: - provider "vkcs" was never configured (versions.tf) - username/password/ project_id/region were declared but wired to nothing; added auth_url and user_domain_name to complete it. - Nova keypairs are per-user, not per-project - added an optional vkcs_compute_keypair resource (var.ssh_public_key) so Terraform can register a keypair under the deploying service account itself. - router_priv_port used a hand-computed fixed_ip offset that collided with VKCS's own auto-created service ports on each network (observed: a "network:dns" port) - now left unset so Neutron's IPAM auto-assigns, which is collision-free by construction. - vkcs_compute_instance set image_id at the top level while also booting from a volume via block_device - the provider docs say not to do this; Nova echoes back a sentinel string for image_id on a volume-booted server, which Terraform read as drift on a ForceNew attribute and wanted to destroy+recreate every already-created instance on every subsequent plan. - private_network_cidrs bumped from /29 to /28 - too tight once the platform's own reserved ports are accounted for. Also removed the priv_srv_01/02/03 demo instances and the LAN network/ security group only they used - this deployment provisions router VMs only, confirmed with the user. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hXR2ftXZZhJ4Y3XuSoR8r
203 lines
6.6 KiB
Terraform
203 lines
6.6 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"
|
|
}
|
|
|
|
# Every VK Cloud project auto-creates a "default" security group with a
|
|
# UUID unique to that project - resolve it dynamically instead of
|
|
# hardcoding one project's UUID. var.default_security_group_id is a
|
|
# last-resort manual override (e.g. non-standard name/SDN in a project).
|
|
data "vkcs_networking_secgroup" "default" {
|
|
name = "default"
|
|
sdn = "sprut"
|
|
}
|
|
|
|
locals {
|
|
default_security_group_id = coalesce(
|
|
var.default_security_group_id,
|
|
data.vkcs_networking_secgroup.default.id
|
|
)
|
|
}
|
|
|
|
# Nova keypairs are per-user, not per-project - a keypair uploaded under a
|
|
# different account is invisible to whichever account Terraform deploys as.
|
|
# Register it here from var.ssh_public_key when supplied; otherwise assume
|
|
# var.ssh_key_name already exists under the deploying account.
|
|
resource "vkcs_compute_keypair" "router" {
|
|
count = var.ssh_public_key != null ? 1 : 0
|
|
name = var.ssh_key_name
|
|
public_key = var.ssh_public_key
|
|
}
|
|
|
|
locals {
|
|
ssh_key_name = var.ssh_public_key != null ? vkcs_compute_keypair.router[0].name : var.ssh_key_name
|
|
}
|
|
|
|
# Security Groups
|
|
resource "vkcs_networking_secgroup" "router_sg" {
|
|
name = "router-sg"
|
|
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 interfaces: one shared private network per admin-supplied
|
|
# CIDR in private_network_cidrs (no VRRP), each router getting its own
|
|
# port/IP inside every such network.
|
|
locals {
|
|
private_roles = [for idx in range(length(var.private_network_cidrs)) : "priv${idx + 1}"]
|
|
|
|
private_network_cidr = {
|
|
for idx, cidr in var.private_network_cidrs :
|
|
"priv${idx + 1}" => cidr
|
|
}
|
|
}
|
|
|
|
resource "vkcs_networking_network" "router_priv_net" {
|
|
for_each = local.private_network_cidr
|
|
name = "router-${each.key}-net"
|
|
sdn = "sprut"
|
|
admin_state_up = true
|
|
}
|
|
|
|
resource "vkcs_networking_subnet" "router_priv_subnet" {
|
|
for_each = local.private_network_cidr
|
|
network_id = vkcs_networking_network.router_priv_net[each.key].id
|
|
name = "router-${each.key}-subnet"
|
|
cidr = each.value
|
|
gateway_ip = cidrhost(each.value, 1)
|
|
sdn = "sprut"
|
|
|
|
# No in-guest DHCP client is needed for these interfaces: with
|
|
# config_drive = true (set on every router instance), cloud-init learns
|
|
# each interface's assigned address from the config-drive metadata and
|
|
# renders it directly, then network-init.sh.tpl re-pins it deterministically
|
|
# to ethN by MAC - no actual DHCP protocol exchange with this subnet ever
|
|
# happens. (Note: this does NOT avoid IP collisions with VKCS's own
|
|
# auto-created service ports on the network, e.g. a "network:dns" port -
|
|
# see router_priv_port below, which leaves ip_address unset instead.)
|
|
enable_dhcp = false
|
|
}
|
|
|
|
resource "vkcs_networking_port" "router_priv_port" {
|
|
for_each = {
|
|
for pair in setproduct(range(var.router_count), local.private_roles) :
|
|
"router${pair[0] + 1}-${pair[1]}" => pair[1]
|
|
}
|
|
name = "router-${each.key}-port"
|
|
network_id = vkcs_networking_network.router_priv_net[each.value].id
|
|
admin_state_up = true
|
|
port_security_enabled = false
|
|
full_security_groups_control = true
|
|
security_group_ids = []
|
|
sdn = "sprut"
|
|
|
|
# No explicit ip_address: let Neutron's IPAM auto-assign one from the
|
|
# subnet's pool. VKCS auto-creates its own service ports on this network
|
|
# (observed: a "network:dns" port) that can silently consume whichever
|
|
# low address a hand-computed offset would have picked, causing
|
|
# IpAddressAlreadyAllocated - IPAM guarantees no double-booking, our own
|
|
# arithmetic doesn't. network-init.sh.tpl matches interfaces by which
|
|
# declared CIDR their live IP falls into, not by an exact expected IP, so
|
|
# the assigned address doesn't need to be known in advance.
|
|
fixed_ip {
|
|
subnet_id = vkcs_networking_subnet.router_priv_subnet[each.value].id
|
|
}
|
|
}
|
|
|
|
resource "vkcs_compute_instance" "router" {
|
|
count = var.router_count
|
|
name = "router${count.index + 1}"
|
|
flavor_name = "STD3-4-4"
|
|
availability_zone = element(var.router_availability_zones, count.index % length(var.router_availability_zones))
|
|
key_pair = local.ssh_key_name
|
|
|
|
security_group_ids = [
|
|
vkcs_networking_secgroup.router_sg.id,
|
|
local.default_security_group_id
|
|
]
|
|
|
|
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.private_network_cidr[role]
|
|
}
|
|
]
|
|
})
|
|
|
|
# WAN: dynamically created port
|
|
network {
|
|
uuid = data.vkcs_networking_network.extnet.id
|
|
}
|
|
|
|
# Private: one pre-created port per role, into that role's shared network
|
|
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
|
|
}
|
|
}
|
|
|