Upcloud: Added support for routers and gateways (#11386)

* Upcloud: Added support for routers and gateways

* Upcloud: Added ipsec properties for UpCloud gateway VPN

* Upcloud: Added support for deprecated network field for loadbalancers
This commit is contained in:
Fredrik Liv
2025-03-15 10:05:46 +01:00
committed by GitHub
parent 986f461ef1
commit 04a8adb17a
8 changed files with 427 additions and 22 deletions

View File

@@ -134,10 +134,40 @@ terraform destroy --var-file cluster-settings.tfvars \
* `end_address`: End of address range to allow * `end_address`: End of address range to allow
* `loadbalancer_enabled`: Enable managed load balancer * `loadbalancer_enabled`: Enable managed load balancer
* `loadbalancer_plan`: Plan to use for load balancer *(development|production-small)* * `loadbalancer_plan`: Plan to use for load balancer *(development|production-small)*
* `loadbalancer_legacy_network`: If the loadbalancer should use the deprecated network field instead of networks blocks. You probably want to have this set to false (default value)
* `loadbalancers`: Ports to load balance and which machines to forward to. Key of this object will be used as the name of the load balancer frontends/backends * `loadbalancers`: Ports to load balance and which machines to forward to. Key of this object will be used as the name of the load balancer frontends/backends
* `port`: Port to load balance. * `port`: Port to load balance.
* `target_port`: Port to the backend servers. * `target_port`: Port to the backend servers.
* `backend_servers`: List of servers that traffic to the port should be forwarded to. * `backend_servers`: List of servers that traffic to the port should be forwarded to.
* `router_enable`: If a router should be connected to the private network or not
* `gateways`: Gateways that should be connected to the router, requires router_enable is set to true
* `features`: List of features for the gateway
* `plan`: Plan to use for the gateway
* `connections`: The connections and tunnel to create for the gateway
* `type`: What type of connection
* `local_routes`: Map of local routes for the connection
* `type`: Type of route
* `static_network`: Destination prefix of the route; needs to be a valid IPv4 prefix
* `remote_routes`: Map of local routes for the connection
* `type`: Type of route
* `static_network`: Destination prefix of the route; needs to be a valid IPv4 prefix
* `tunnels`: The tunnels to create for this connection
* `remote_address`: The remote address for the tunnel
* `ipsec_properties`: Set properties of IPSec, if not set, defaults will be used
* `child_rekey_time`: IKE child SA rekey time in seconds
* `dpd_delay`: Delay before sending Dead Peer Detection packets if no traffic is detected, in seconds
* `dpd_timeout`: Timeout period for DPD reply before considering the peer to be dead, in seconds
* `ike_lifetime`: Maximum IKE SA lifetime in seconds()
* `rekey_time`: IKE SA rekey time in seconds
* `phase1_algorithms`: List of Phase 1: Proposal algorithms
* `phase1_dh_group_numbers`: List of Phase 1 Diffie-Hellman group numbers
* `phase1_integrity_algorithms`: List of Phase 1 integrity algorithms
* `phase2_algorithms`: List of Phase 2: Security Association algorithms
* `phase2_dh_group_numbers`: List of Phase 2 Diffie-Hellman group numbers
* `phase2_integrity_algorithms`: List of Phase 2 integrity algorithms
* `gateway_vpn_psks`: Separate variable for providing psks for connection tunnels. Environment variable can be exported in the following format `export TF_VAR_gateway_vpn_psks='{"${gateway-name}-${connecton-name}-tunnel":{psk:"..."}}'`
* `static_routes`: Static routes to apply to the router, requires `router_enable` is set to true
* `network_peerings`: Other UpCloud private networks to peer with, requires `router_enable` is set to true
* `server_groups`: Group servers together * `server_groups`: Group servers together
* `servers`: The servers that should be included in the group. * `servers`: The servers that should be included in the group.
* `anti_affinity_policy`: Defines if a server group is an anti-affinity group. Setting this to "strict" or yes" will result in all servers in the group being placed on separate compute hosts. The value can be "strict", "yes" or "no". "strict" refers to strict policy doesn't allow servers in the same server group to be on the same host. "yes" refers to best-effort policy and tries to put servers on different hosts, but this is not guaranteed. * `anti_affinity_policy`: Defines if a server group is an anti-affinity group. Setting this to "strict" or yes" will result in all servers in the group being placed on separate compute hosts. The value can be "strict", "yes" or "no". "strict" refers to strict policy doesn't allow servers in the same server group to be on the same host. "yes" refers to best-effort policy and tries to put servers on different hosts, but this is not guaranteed.

View File

@@ -153,3 +153,46 @@ server_groups = {
# anti_affinity_policy = "yes" # anti_affinity_policy = "yes"
# } # }
} }
router_enable = false
gateways = {
# "gateway" : {
# features: [ "vpn" ]
# plan = "production"
# connections = {
# "connection" = {
# name = "connection"
# type = "ipsec"
# remote_routes = {
# "them" = {
# type = "static"
# static_network = "1.2.3.4/24"
# }
# }
# local_routes = {
# "me" = {
# type = "static"
# static_network = "4.3.2.1/24"
# }
# }
# tunnels = {
# "tunnel1" = {
# remote_address = "1.2.3.4"
# }
# }
# }
# }
# }
}
# gateway_vpn_psks = {} # Should be loaded as an environment variable
static_routes = {
# "route": {
# route: "1.2.3.4/24"
# nexthop: "4.3.2.1"
# }
}
network_peerings = {
# "peering": {
# remote_network: "uuid"
# }
}

View File

@@ -36,8 +36,15 @@ module "kubernetes" {
loadbalancer_enabled = var.loadbalancer_enabled loadbalancer_enabled = var.loadbalancer_enabled
loadbalancer_plan = var.loadbalancer_plan loadbalancer_plan = var.loadbalancer_plan
loadbalancer_outbound_proxy_protocol = var.loadbalancer_proxy_protocol ? "v2" : "" loadbalancer_outbound_proxy_protocol = var.loadbalancer_proxy_protocol ? "v2" : ""
loadbalancer_legacy_network = var.loadbalancer_legacy_network
loadbalancers = var.loadbalancers loadbalancers = var.loadbalancers
router_enable = var.router_enable
gateways = var.gateways
gateway_vpn_psks = var.gateway_vpn_psks
static_routes = var.static_routes
network_peerings = var.network_peerings
server_groups = var.server_groups server_groups = var.server_groups
} }

View File

@@ -20,6 +20,36 @@ locals {
] ]
]) ])
gateway_connections = flatten([
for gateway_name, gateway in var.gateways : [
for connection_name, connection in gateway.connections : {
"gateway_id" = upcloud_gateway.gateway[gateway_name].id
"gateway_name" = gateway_name
"connection_name" = connection_name
"type" = connection.type
"local_routes" = connection.local_routes
"remote_routes" = connection.remote_routes
}
]
])
gateway_connection_tunnels = flatten([
for gateway_name, gateway in var.gateways : [
for connection_name, connection in gateway.connections : [
for tunnel_name, tunnel in connection.tunnels : {
"gateway_id" = upcloud_gateway.gateway[gateway_name].id
"gateway_name" = gateway_name
"connection_id" = upcloud_gateway_connection.gateway_connection["${gateway_name}-${connection_name}"].id
"connection_name" = connection_name
"tunnel_name" = tunnel_name
"local_address_name" = tolist(upcloud_gateway.gateway[gateway_name].address).0.name
"remote_address" = tunnel.remote_address
"ipsec_properties" = tunnel.ipsec_properties
}
]
]
])
# If prefix is set, all resources will be prefixed with "${var.prefix}-" # If prefix is set, all resources will be prefixed with "${var.prefix}-"
# Else don't prefix with anything # Else don't prefix with anything
resource-prefix = "%{if var.prefix != ""}${var.prefix}-%{endif}" resource-prefix = "%{if var.prefix != ""}${var.prefix}-%{endif}"
@@ -31,9 +61,12 @@ resource "upcloud_network" "private" {
ip_network { ip_network {
address = var.private_network_cidr address = var.private_network_cidr
dhcp_default_route = var.router_enable
dhcp = true dhcp = true
family = "IPv4" family = "IPv4"
} }
router = var.router_enable ? upcloud_router.router[0].id : null
} }
resource "upcloud_storage" "additional_disks" { resource "upcloud_storage" "additional_disks" {
@@ -516,19 +549,34 @@ resource "upcloud_loadbalancer" "lb" {
name = "${local.resource-prefix}lb" name = "${local.resource-prefix}lb"
plan = var.loadbalancer_plan plan = var.loadbalancer_plan
zone = var.private_cloud ? var.public_zone : var.zone zone = var.private_cloud ? var.public_zone : var.zone
networks { network = var.loadbalancer_legacy_network ? upcloud_network.private.id : null
dynamic "networks" {
for_each = var.loadbalancer_legacy_network ? [] : [1]
content {
name = "Private-Net" name = "Private-Net"
type = "private" type = "private"
family = "IPv4" family = "IPv4"
network = upcloud_network.private.id network = upcloud_network.private.id
} }
networks { }
dynamic "networks" {
for_each = var.loadbalancer_legacy_network ? [] : [1]
content {
name = "Public-Net" name = "Public-Net"
type = "public" type = "public"
family = "IPv4" family = "IPv4"
} }
} }
lifecycle {
ignore_changes = [ maintenance_dow, maintenance_time ]
}
}
resource "upcloud_loadbalancer_backend" "lb_backend" { resource "upcloud_loadbalancer_backend" "lb_backend" {
for_each = var.loadbalancer_enabled ? var.loadbalancers : {} for_each = var.loadbalancer_enabled ? var.loadbalancers : {}
@@ -547,11 +595,24 @@ resource "upcloud_loadbalancer_frontend" "lb_frontend" {
mode = "tcp" mode = "tcp"
port = each.value.port port = each.value.port
default_backend_name = upcloud_loadbalancer_backend.lb_backend[each.key].name default_backend_name = upcloud_loadbalancer_backend.lb_backend[each.key].name
networks {
dynamic "networks" {
for_each = var.loadbalancer_legacy_network ? [] : [1]
content {
name = "Public-Net" name = "Public-Net"
} }
} }
dynamic "networks" {
for_each = each.value.allow_internal_frontend ? [1] : []
content{
name = "Private-Net"
}
}
}
resource "upcloud_loadbalancer_static_backend_member" "lb_backend_member" { resource "upcloud_loadbalancer_static_backend_member" "lb_backend_member" {
for_each = { for_each = {
for be_server in local.lb_backend_servers : for be_server in local.lb_backend_servers :
@@ -579,3 +640,111 @@ resource "upcloud_server_group" "server_groups" {
ignore_changes = [members] ignore_changes = [members]
} }
} }
resource "upcloud_router" "router" {
count = var.router_enable ? 1 : 0
name = "${local.resource-prefix}router"
dynamic "static_route" {
for_each = var.static_routes
content {
name = static_route.key
nexthop = static_route.value["nexthop"]
route = static_route.value["route"]
}
}
}
resource "upcloud_gateway" "gateway" {
for_each = var.router_enable ? var.gateways : {}
name = "${local.resource-prefix}${each.key}-gateway"
zone = var.zone
features = each.value.features
plan = each.value.plan
router {
id = upcloud_router.router[0].id
}
}
resource "upcloud_gateway_connection" "gateway_connection" {
for_each = {
for gc in local.gateway_connections : "${gc.gateway_name}-${gc.connection_name}" => gc
}
gateway = each.value.gateway_id
name = "${local.resource-prefix}${each.key}-gateway-connection"
type = each.value.type
dynamic "local_route" {
for_each = each.value.local_routes
content {
name = local_route.key
type = local_route.value["type"]
static_network = local_route.value["static_network"]
}
}
dynamic "remote_route" {
for_each = each.value.remote_routes
content {
name = remote_route.key
type = remote_route.value["type"]
static_network = remote_route.value["static_network"]
}
}
}
resource "upcloud_gateway_connection_tunnel" "gateway_connection_tunnel" {
for_each = {
for gct in local.gateway_connection_tunnels : "${gct.gateway_name}-${gct.connection_name}-${gct.tunnel_name}-tunnel" => gct
}
connection_id = each.value.connection_id
name = each.key
local_address_name = each.value.local_address_name
remote_address = each.value.remote_address
ipsec_auth_psk {
psk = var.gateway_vpn_psks[each.key].psk
}
dynamic "ipsec_properties" {
for_each = each.value.ipsec_properties != null ? { "ip": each.value.ipsec_properties } : {}
content {
child_rekey_time = ipsec_properties.value["child_rekey_time"]
dpd_delay = ipsec_properties.value["dpd_delay"]
dpd_timeout = ipsec_properties.value["dpd_timeout"]
ike_lifetime = ipsec_properties.value["ike_lifetime"]
rekey_time = ipsec_properties.value["rekey_time"]
phase1_algorithms = ipsec_properties.value["phase1_algorithms"]
phase1_dh_group_numbers = ipsec_properties.value["phase1_dh_group_numbers"]
phase1_integrity_algorithms = ipsec_properties.value["phase1_integrity_algorithms"]
phase2_algorithms = ipsec_properties.value["phase2_algorithms"]
phase2_dh_group_numbers = ipsec_properties.value["phase2_dh_group_numbers"]
phase2_integrity_algorithms = ipsec_properties.value["phase2_integrity_algorithms"]
}
}
}
resource "upcloud_network_peering" "peering" {
for_each = var.network_peerings
name = "${local.resource-prefix}${each.key}"
network {
uuid = upcloud_network.private.id
}
peer_network {
uuid = each.value.remote_network
}
}

View File

@@ -98,12 +98,18 @@ variable "loadbalancer_outbound_proxy_protocol" {
type = string type = string
} }
variable "loadbalancer_legacy_network" {
type = bool
default = false
}
variable "loadbalancers" { variable "loadbalancers" {
description = "Load balancers" description = "Load balancers"
type = map(object({ type = map(object({
port = number port = number
target_port = number target_port = number
allow_internal_frontend = optional(bool)
backend_servers = list(string) backend_servers = list(string)
})) }))
} }
@@ -115,3 +121,72 @@ variable "server_groups" {
anti_affinity_policy = string anti_affinity_policy = string
})) }))
} }
variable "router_enable" {
description = "If a router should be enabled and connected to the private network or not"
type = bool
}
variable "gateways" {
description = "Gateways that should be connected to the router, requires router_enable is set to true"
type = map(object({
features = list(string)
plan = optional(string)
connections = optional(map(object({
type = string
local_routes = optional(map(object({
type = string
static_network = string
})))
remote_routes = optional(map(object({
type = string
static_network = string
})))
tunnels = optional(map(object({
remote_address = string
ipsec_properties = optional(object({
child_rekey_time = number
dpd_delay = number
dpd_timeout = number
ike_lifetime = number
rekey_time = number
phase1_algorithms = set(string)
phase1_dh_group_numbers = set(string)
phase1_integrity_algorithms = set(string)
phase2_algorithms = set(string)
phase2_dh_group_numbers = set(string)
phase2_integrity_algorithms = set(string)
}))
})))
})))
}))
}
variable "gateway_vpn_psks" {
description = "Separate variable for providing psks for connection tunnels"
type = map(object({
psk = string
}))
default = {}
sensitive = true
}
variable "static_routes" {
description = "Static routes to apply to the router, requires router_enable is set to true"
type = map(object({
nexthop = string
route = string
}))
}
variable "network_peerings" {
description = "Other UpCloud private networks to peer with, requires router_enable is set to true"
type = map(object({
remote_network = string
}))
}

View File

@@ -3,7 +3,7 @@ terraform {
required_providers { required_providers {
upcloud = { upcloud = {
source = "UpCloudLtd/upcloud" source = "UpCloudLtd/upcloud"
version = "~>5.6.0" version = "~>5.9.0"
} }
} }
required_version = ">= 0.13" required_version = ">= 0.13"

View File

@@ -136,12 +136,20 @@ variable "loadbalancer_proxy_protocol" {
default = false default = false
} }
variable "loadbalancer_legacy_network" {
description = "If the loadbalancer should use the deprecated network field instead of networks blocks. You probably want to have this set to false"
type = bool
default = false
}
variable "loadbalancers" { variable "loadbalancers" {
description = "Load balancers" description = "Load balancers"
type = map(object({ type = map(object({
port = number port = number
target_port = number target_port = number
allow_internal_frontend = optional(bool, false)
backend_servers = list(string) backend_servers = list(string)
})) }))
default = {} default = {}
@@ -156,3 +164,76 @@ variable "server_groups" {
default = {} default = {}
} }
variable "router_enable" {
description = "If a router should be enabled and connected to the private network or not"
type = bool
default = false
}
variable "gateways" {
description = "Gateways that should be connected to the router, requires router_enable is set to true"
type = map(object({
features = list(string)
plan = optional(string)
connections = optional(map(object({
type = string
local_routes = optional(map(object({
type = string
static_network = string
})), {})
remote_routes = optional(map(object({
type = string
static_network = string
})), {})
tunnels = optional(map(object({
remote_address = string
ipsec_properties = optional(object({
child_rekey_time = number
dpd_delay = number
dpd_timeout = number
ike_lifetime = number
rekey_time = number
phase1_algorithms = set(string)
phase1_dh_group_numbers = set(string)
phase1_integrity_algorithms = set(string)
phase2_algorithms = set(string)
phase2_dh_group_numbers = set(string)
phase2_integrity_algorithms = set(string)
}))
})), {})
})), {})
}))
default = {}
}
variable "gateway_vpn_psks" {
description = "Separate variable for providing psks for connection tunnels"
type = map(object({
psk = string
}))
default = {}
sensitive = true
}
variable "static_routes" {
description = "Static routes to apply to the router, requires router_enable is set to true"
type = map(object({
nexthop = string
route = string
}))
default = {}
}
variable "network_peerings" {
description = "Other UpCloud private networks to peer with, requires router_enable is set to true"
type = map(object({
remote_network = string
}))
default = {}
}

View File

@@ -3,7 +3,7 @@ terraform {
required_providers { required_providers {
upcloud = { upcloud = {
source = "UpCloudLtd/upcloud" source = "UpCloudLtd/upcloud"
version = "~>5.6.0" version = "~>5.9.0"
} }
} }
required_version = ">= 0.13" required_version = ">= 0.13"