feat: Add private network architecture with NAT gateway
Enable deployment of client servers without public IPs using private network (10.0.0.0/16) with NAT gateway via edge server. ## Infrastructure Changes: ### Terraform (tofu/): - **network.tf**: Define private network and subnet (10.0.0.0/24) - NAT gateway route through edge server - Firewall rules for client servers - **main.tf**: Support private-only servers - Optional public_ip_enabled flag per client - Dynamic network block for private IP assignment - User-data templates for public vs private servers - **user-data-*.yml**: Cloud-init templates - Private servers: Configure default route via NAT gateway - Public servers: Standard configuration - **dns.tf**: Update DNS to support edge routing - Client domains point to edge server IP - Wildcard DNS for subdomains - **variables.tf**: Add private_ip and public_ip_enabled options ### Ansible: - **deploy.yml**: Add diun and kuma roles to deployment ## Benefits: - Cost savings: No public IP needed for each client - Scalability: No public IP exhaustion limits - Security: Clients not directly exposed to internet - Centralized SSL: All TLS termination at edge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
13685eb454
commit
79635eeece
7 changed files with 172 additions and 20 deletions
|
|
@ -66,6 +66,10 @@
|
|||
- role: mailgun
|
||||
- role: authentik
|
||||
- role: nextcloud
|
||||
- role: diun
|
||||
tags: diun
|
||||
- role: kuma
|
||||
tags: kuma
|
||||
|
||||
post_tasks:
|
||||
- name: Display deployment summary
|
||||
|
|
|
|||
18
tofu/dns.tf
18
tofu/dns.tf
|
|
@ -6,7 +6,8 @@ data "hcloud_zone" "main" {
|
|||
name = var.base_domain
|
||||
}
|
||||
|
||||
# A Records for client servers (e.g., test.vrije.cloud -> 78.47.191.38)
|
||||
# A Records for client servers with public IPs (e.g., test.vrije.cloud -> 78.47.191.38)
|
||||
# Clients without public IPs (behind edge proxy) point to edge server instead
|
||||
resource "hcloud_zone_rrset" "client_a" {
|
||||
for_each = var.clients
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ resource "hcloud_zone_rrset" "client_a" {
|
|||
ttl = 300
|
||||
records = [
|
||||
{
|
||||
value = hcloud_server.client[each.key].ipv4_address
|
||||
comment = "Client ${each.key} server"
|
||||
value = lookup(each.value, "public_ip_enabled", true) ? hcloud_server.client[each.key].ipv4_address : hcloud_server.edge.ipv4_address
|
||||
comment = lookup(each.value, "public_ip_enabled", true) ? "Client ${each.key} server" : "Client ${each.key} via edge proxy"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -32,15 +33,18 @@ resource "hcloud_zone_rrset" "client_wildcard" {
|
|||
ttl = 300
|
||||
records = [
|
||||
{
|
||||
value = hcloud_server.client[each.key].ipv4_address
|
||||
comment = "Wildcard for ${each.key} subdomains (Zitadel, Nextcloud, etc)"
|
||||
value = lookup(each.value, "public_ip_enabled", true) ? hcloud_server.client[each.key].ipv4_address : hcloud_server.edge.ipv4_address
|
||||
comment = lookup(each.value, "public_ip_enabled", true) ? "Wildcard for ${each.key} subdomains" : "Wildcard for ${each.key} via edge proxy"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# AAAA Records for IPv6 (e.g., test.vrije.cloud IPv6)
|
||||
# AAAA Records for IPv6 (only for servers with public IPs)
|
||||
resource "hcloud_zone_rrset" "client_aaaa" {
|
||||
for_each = var.clients
|
||||
for_each = {
|
||||
for k, v in var.clients : k => v
|
||||
if lookup(v, "public_ip_enabled", true)
|
||||
}
|
||||
|
||||
zone = data.hcloud_zone.main.name
|
||||
name = each.value.subdomain
|
||||
|
|
|
|||
33
tofu/main.tf
33
tofu/main.tf
|
|
@ -70,18 +70,25 @@ resource "hcloud_server" "client" {
|
|||
# Enable backups if requested
|
||||
backups = var.enable_snapshots
|
||||
|
||||
# Public network configuration (can be disabled for private-only servers)
|
||||
public_net {
|
||||
ipv4_enabled = lookup(each.value, "public_ip_enabled", true)
|
||||
ipv6_enabled = lookup(each.value, "public_ip_enabled", true)
|
||||
}
|
||||
|
||||
# Private network (required for servers without public IP)
|
||||
dynamic "network" {
|
||||
for_each = lookup(each.value, "private_ip", null) != null ? [1] : []
|
||||
content {
|
||||
network_id = hcloud_network.private.id
|
||||
ip = each.value.private_ip
|
||||
}
|
||||
}
|
||||
|
||||
# User data for initial setup
|
||||
user_data = <<-EOF
|
||||
#cloud-config
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- python3
|
||||
- python3-pip
|
||||
runcmd:
|
||||
- hostnamectl set-hostname ${each.key}
|
||||
EOF
|
||||
user_data = lookup(each.value, "public_ip_enabled", true) == false ? templatefile("${path.module}/user-data-private.yml", {
|
||||
hostname = each.key
|
||||
}) : templatefile("${path.module}/user-data-public.yml", {
|
||||
hostname = each.key
|
||||
})
|
||||
}
|
||||
|
|
|
|||
86
tofu/network.tf
Normal file
86
tofu/network.tf
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Private Network Configuration
|
||||
# Enables client servers to communicate without public IPs
|
||||
|
||||
# Private Network
|
||||
resource "hcloud_network" "private" {
|
||||
name = "client-private-network"
|
||||
ip_range = "10.0.0.0/16"
|
||||
|
||||
labels = {
|
||||
managed = "terraform"
|
||||
purpose = "client-internal"
|
||||
}
|
||||
}
|
||||
|
||||
# Subnet for client servers
|
||||
resource "hcloud_network_subnet" "clients" {
|
||||
network_id = hcloud_network.private.id
|
||||
type = "cloud"
|
||||
network_zone = "eu-central"
|
||||
ip_range = "10.0.0.0/24"
|
||||
}
|
||||
|
||||
# Note: Client servers attach to private network via main.tf dynamic block
|
||||
|
||||
# Edge Server Configuration
|
||||
# Single public-facing reverse proxy for all clients
|
||||
|
||||
# SSH key for edge server
|
||||
resource "hcloud_ssh_key" "edge" {
|
||||
name = "edge-server-deploy-key"
|
||||
public_key = file("${path.module}/../keys/ssh/edge.pub")
|
||||
}
|
||||
|
||||
# Edge server (public IP + private network)
|
||||
resource "hcloud_server" "edge" {
|
||||
name = "edge"
|
||||
server_type = var.edge_server_type
|
||||
image = "ubuntu-24.04"
|
||||
location = var.edge_location
|
||||
ssh_keys = [hcloud_ssh_key.edge.id]
|
||||
firewall_ids = [hcloud_firewall.client_firewall.id]
|
||||
|
||||
labels = {
|
||||
role = "edge-proxy"
|
||||
managed = "terraform"
|
||||
}
|
||||
|
||||
# Enable backups
|
||||
backups = var.enable_snapshots
|
||||
|
||||
# User data for initial setup
|
||||
user_data = <<-EOF
|
||||
#cloud-config
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- python3
|
||||
- python3-pip
|
||||
runcmd:
|
||||
- hostnamectl set-hostname edge
|
||||
EOF
|
||||
|
||||
# Ensure public network is enabled
|
||||
public_net {
|
||||
ipv4_enabled = true
|
||||
ipv6_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
# Attach edge server to private network
|
||||
resource "hcloud_server_network" "edge" {
|
||||
server_id = hcloud_server.edge.id
|
||||
network_id = hcloud_network.private.id
|
||||
ip = "10.0.0.2" # Fixed IP for edge server (10.0.0.1 is gateway)
|
||||
}
|
||||
|
||||
# NAT Gateway Route
|
||||
# Routes all internet-bound traffic from private network through edge server
|
||||
resource "hcloud_network_route" "nat_gateway" {
|
||||
network_id = hcloud_network.private.id
|
||||
destination = "0.0.0.0/0"
|
||||
gateway = "10.0.0.2" # Edge server acts as NAT gateway
|
||||
}
|
||||
25
tofu/user-data-private.yml
Normal file
25
tofu/user-data-private.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#cloud-config
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- python3
|
||||
- python3-pip
|
||||
runcmd:
|
||||
- hostnamectl set-hostname ${hostname}
|
||||
- |
|
||||
# Configure default route for private-only server
|
||||
# Hetzner network route forwards traffic to edge gateway (10.0.0.2)
|
||||
cat > /etc/netplan/60-private-network.yaml <<'NETPLAN'
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
enp7s0:
|
||||
routes:
|
||||
- to: default
|
||||
via: 10.0.0.1
|
||||
NETPLAN
|
||||
chmod 600 /etc/netplan/60-private-network.yaml
|
||||
netplan apply
|
||||
11
tofu/user-data-public.yml
Normal file
11
tofu/user-data-public.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#cloud-config
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- python3
|
||||
- python3-pip
|
||||
runcmd:
|
||||
- hostnamectl set-hostname ${hostname}
|
||||
|
|
@ -31,10 +31,25 @@ variable "clients" {
|
|||
subdomain = string # e.g., "alpha" for alpha.platform.nl
|
||||
apps = list(string) # e.g., ["zitadel", "nextcloud"]
|
||||
nextcloud_volume_size = number # Size in GB for Nextcloud data volume (min 10, max 10000)
|
||||
private_ip = optional(string) # Private IP in 10.0.0.0/24 range (e.g., "10.0.0.10")
|
||||
public_ip_enabled = optional(bool, true) # Whether to enable public IP (default: true for backward compatibility)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
# Edge Server Configuration
|
||||
variable "edge_server_type" {
|
||||
description = "Server type for edge proxy server"
|
||||
type = string
|
||||
default = "cpx22" # 3 vCPU, 4 GB RAM - CPX11/21 unavailable in fsn1
|
||||
}
|
||||
|
||||
variable "edge_location" {
|
||||
description = "Location for edge proxy server"
|
||||
type = string
|
||||
default = "fsn1" # Falkenstein, Germany
|
||||
}
|
||||
|
||||
# Enable automated snapshots
|
||||
variable "enable_snapshots" {
|
||||
description = "Enable automated daily snapshots (20% of server cost)"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue