Add Hetzner vSwitch network for BRouter relocation
Adds an OpenSpec change scoping the relocation of BRouter from the co-located flagship (cx23, 4 GB RAM, 40 GB SSD, Europe segments only) to a dedicated Hetzner Robot host in the same datacenter, with private connectivity over Hetzner vSwitch #80672 (VLAN 4000). This first PR only lays the network prerequisite: - Terraform: a Hetzner Cloud Network (10.0.0.0/16) with a cloud subnet (10.0.0.0/24) hosting the flagship at 10.0.0.2, and a vSwitch subnet (10.0.1.0/24) bridged to Robot VLAN 4000. The dedicated host's VLAN sub-interface (10.0.1.10 on enp4s0.4000) is configured out-of-band via netplan and is not Terraform-managed. - lifecycle { ignore_changes = [user_data] } on the flagship server to prevent the Hetzner provider's post-1.45 user_data hash drift from triggering a spurious full-server replacement on unrelated applies. - OpenSpec change with proposal, design, specs (delta for brouter-integration / infrastructure / observability / security-hardening), and tasks; Section 1 (pre-flight) is checked off with operator notes. Verification: ping both directions across the vSwitch is 0% loss, sub-ms latency; dedicated host's VLAN config persists across reboot (verified ~60 s to restore private reachability). Follow-up PRs will land the BRouter host compose project, Planner shared-secret header, CD workflow retarget, observability wiring, and the cutover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
566a322dbb
commit
102a744e67
10 changed files with 465 additions and 0 deletions
|
|
@ -54,6 +54,14 @@ resource "hcloud_server" "trails" {
|
|||
SSHD
|
||||
systemctl reload ssh
|
||||
EOF
|
||||
|
||||
# Hetzner Cloud provider's user_data hash algorithm changed in 1.45+,
|
||||
# causing spurious replacement diffs on every plan even when the script
|
||||
# is unchanged. user_data only runs on first boot anyway — changes after
|
||||
# provisioning have no effect — so we pin the attribute to ignore_changes.
|
||||
lifecycle {
|
||||
ignore_changes = [user_data]
|
||||
}
|
||||
}
|
||||
|
||||
resource "hcloud_firewall" "trails" {
|
||||
|
|
@ -86,6 +94,55 @@ resource "hcloud_firewall_attachment" "trails" {
|
|||
server_ids = [hcloud_server.trails.id]
|
||||
}
|
||||
|
||||
# Private network bridging the flagship (Hetzner Cloud) to the dedicated
|
||||
# BRouter host (Hetzner Robot) via vSwitch. Both sides must be in the same
|
||||
# location (fsn1 / eu-central).
|
||||
#
|
||||
# Hetzner requires Cloud servers to attach to a regular "cloud" subnet;
|
||||
# the "vswitch" subnet is VLAN-only and cannot host Cloud NICs. The two
|
||||
# subnets live side by side in the same network and Hetzner routes between
|
||||
# them automatically.
|
||||
#
|
||||
# 10.0.0.0/16 — overall private network
|
||||
# 10.0.0.1 — network gateway (reserved by Hetzner; not usable)
|
||||
# 10.0.0.0/24 — cloud subnet (hosts Cloud server NICs)
|
||||
# 10.0.0.2 — flagship (assigned below)
|
||||
# 10.0.1.0/24 — vSwitch subnet (bridged to Robot VLAN)
|
||||
# 10.0.1.10 — dedicated BRouter host (configured on the Robot side
|
||||
# as a VLAN sub-interface, not managed by Terraform)
|
||||
|
||||
resource "hcloud_network" "private" {
|
||||
name = "trails-cool"
|
||||
ip_range = "10.0.0.0/16"
|
||||
|
||||
labels = {
|
||||
project = "trails-cool"
|
||||
}
|
||||
}
|
||||
|
||||
resource "hcloud_network_subnet" "cloud" {
|
||||
network_id = hcloud_network.private.id
|
||||
type = "cloud"
|
||||
network_zone = "eu-central"
|
||||
ip_range = "10.0.0.0/24"
|
||||
}
|
||||
|
||||
resource "hcloud_network_subnet" "vswitch" {
|
||||
network_id = hcloud_network.private.id
|
||||
type = "vswitch"
|
||||
network_zone = "eu-central"
|
||||
ip_range = "10.0.1.0/24"
|
||||
vswitch_id = var.vswitch_id
|
||||
}
|
||||
|
||||
resource "hcloud_server_network" "trails" {
|
||||
server_id = hcloud_server.trails.id
|
||||
network_id = hcloud_network.private.id
|
||||
ip = "10.0.0.2"
|
||||
|
||||
depends_on = [hcloud_network_subnet.cloud]
|
||||
}
|
||||
|
||||
# Reverse DNS
|
||||
|
||||
resource "hcloud_rdns" "trails_ipv4" {
|
||||
|
|
@ -361,3 +418,8 @@ output "domain" {
|
|||
output "planner_domain" {
|
||||
value = "planner.trails.cool"
|
||||
}
|
||||
|
||||
output "flagship_private_ip" {
|
||||
value = hcloud_server_network.trails.ip
|
||||
description = "Private IP of the flagship on the vSwitch network (reachable from the dedicated BRouter host)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,9 @@ variable "hcloud_token" {
|
|||
variable "ssh_public_key" {
|
||||
description = "SSH public key for server access"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vswitch_id" {
|
||||
description = "Hetzner Robot vSwitch ID bridging the flagship to the dedicated BRouter host (see robot.hetzner.com/vswitch)"
|
||||
type = number
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue