feat: render Talos bootstrap configuration
This commit is contained in:
parent
cc0ceb700d
commit
9cfcb87db0
3
terraform/.gitignore
vendored
3
terraform/.gitignore
vendored
|
|
@ -11,7 +11,8 @@ crash.log
|
||||||
crash.*.log
|
crash.*.log
|
||||||
|
|
||||||
# Ignore local Terraform configuration files that can contain sensitive data.
|
# Ignore local Terraform configuration files that can contain sensitive data.
|
||||||
#terraform.tfvars
|
terraform.tfvars
|
||||||
|
.maidn/
|
||||||
|
|
||||||
# Ignore override files as they are typically used for local testing.
|
# Ignore override files as they are typically used for local testing.
|
||||||
# These should not be checked in to source control.
|
# These should not be checked in to source control.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ locals {
|
||||||
all_networks_flat = flatten([
|
all_networks_flat = flatten([
|
||||||
for node in var.nodes : [
|
for node in var.nodes : [
|
||||||
for network in node.networks : {
|
for network in node.networks : {
|
||||||
proxmox_node = lookup(node, "proxmox_node", var.proxmox_node)
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node)
|
||||||
vlan_id = network.vlan_id
|
vlan_id = network.vlan_id
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -57,10 +57,10 @@ locals {
|
||||||
cores = node.cores
|
cores = node.cores
|
||||||
memory = node.memory
|
memory = node.memory
|
||||||
role = node.role
|
role = node.role
|
||||||
proxmox_node = lookup(node, "proxmox_node", var.proxmox_node)
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node)
|
||||||
disk_size = node.disk_size
|
disk_size = node.disk_size
|
||||||
disk_storage = var.disk_storage
|
disk_storage = var.disk_storage
|
||||||
tags = lookup(node, "tags", [node.role])
|
tags = coalesce(node.tags, [node.role])
|
||||||
iso_file = local.node_iso_files[node.name]
|
iso_file = local.node_iso_files[node.name]
|
||||||
additional_disk_size = lookup(node, "additional_disk_size", null)
|
additional_disk_size = lookup(node, "additional_disk_size", null)
|
||||||
additional_disk_storage = lookup(node, "additional_disk_size", null) != null ? var.additional_disk_storage : null
|
additional_disk_storage = lookup(node, "additional_disk_size", null) != null ? var.additional_disk_storage : null
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ resource "null_resource" "stage_talos_image" {
|
||||||
|
|
||||||
triggers = {
|
triggers = {
|
||||||
node_name = each.value.name
|
node_name = each.value.name
|
||||||
proxmox_node = lookup(each.value, "proxmox_node", var.proxmox_node)
|
proxmox_node = coalesce(each.value.proxmox_node, var.proxmox_node)
|
||||||
image_url = local.talos_iso_download_url
|
image_url = local.talos_iso_download_url
|
||||||
storage = var.talos_image_storage
|
storage = var.talos_image_storage
|
||||||
talos_version = var.talos_version
|
talos_version = var.talos_version
|
||||||
|
|
@ -31,19 +31,26 @@ resource "null_resource" "stage_talos_image" {
|
||||||
|
|
||||||
provisioner "local-exec" {
|
provisioner "local-exec" {
|
||||||
interpreter = ["python", "-c"]
|
interpreter = ["python", "-c"]
|
||||||
command = "import subprocess; subprocess.run(['python', r'${path.module}/scripts/stage_talos_image.py', '--api-url', '${var.proxmox_api_url}', '--api-token', '${var.proxmox_api_token}', '--node', '${self.triggers.proxmox_node}', '--storage', '${var.talos_image_storage}', '--image-url', '${local.talos_iso_download_url}', '--filename', '${self.triggers.filename}', '--force'], check=True)"
|
environment = {
|
||||||
|
MAIDN_PROXMOX_API_TOKEN = var.proxmox_api_token
|
||||||
|
}
|
||||||
|
command = "import subprocess; subprocess.run(['python', r'${path.module}/scripts/stage_talos_image.py', '--api-url', '${var.proxmox_api_url}', '--node', '${self.triggers.proxmox_node}', '--storage', '${var.talos_image_storage}', '--image-url', '${local.talos_iso_download_url}', '--filename', '${self.triggers.filename}'], check=True)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create all required network bridges
|
# Create all required network bridges
|
||||||
resource "proxmox_virtual_environment_network_linux_bridge" "cluster_bridge" {
|
resource "proxmox_virtual_environment_network_linux_bridge" "cluster_bridge" {
|
||||||
for_each = local.unique_bridges
|
for_each = var.manage_network_bridges ? local.unique_bridges : {}
|
||||||
|
|
||||||
node_name = each.value.proxmox_node
|
node_name = each.value.proxmox_node
|
||||||
name = each.value.bridge_name
|
name = each.value.bridge_name
|
||||||
comment = "Auto-created bridge for Talos VLAN ${each.value.vlan_id} on ${each.value.proxmox_node}"
|
comment = "Auto-created bridge for Talos VLAN ${each.value.vlan_id} on ${each.value.proxmox_node}"
|
||||||
ports = var.create_vlan_interface ? ["${each.value.phys_iface}.${each.value.vlan_id}"] : [each.value.phys_iface]
|
ports = var.create_vlan_interface ? ["${each.value.phys_iface}.${each.value.vlan_id}"] : [each.value.phys_iface]
|
||||||
vlan_aware = false
|
vlan_aware = false
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create the Virtual Machines
|
# Create the Virtual Machines
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -82,13 +83,15 @@ def wait_for_storage_content(base_url, token, node, storage, filename):
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--api-url", required=True)
|
parser.add_argument("--api-url", required=True)
|
||||||
parser.add_argument("--api-token", required=True)
|
parser.add_argument("--api-token", default=os.environ.get("MAIDN_PROXMOX_API_TOKEN"))
|
||||||
parser.add_argument("--node", required=True)
|
parser.add_argument("--node", required=True)
|
||||||
parser.add_argument("--storage", required=True)
|
parser.add_argument("--storage", required=True)
|
||||||
parser.add_argument("--image-url", required=True)
|
parser.add_argument("--image-url", required=True)
|
||||||
parser.add_argument("--filename", required=True)
|
parser.add_argument("--filename", required=True)
|
||||||
parser.add_argument("--force", action="store_true")
|
parser.add_argument("--force", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if not args.api_token:
|
||||||
|
parser.error("--api-token or MAIDN_PROXMOX_API_TOKEN is required")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
filename, downloaded = ensure_image(args.api_url, args.api_token, args.node, args.storage, args.image_url, args.filename, args.force)
|
filename, downloaded = ensure_image(args.api_url, args.api_token, args.node, args.storage, args.image_url, args.filename, args.force)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ resource "local_file" "talconfig" {
|
||||||
cluster_endpoint = local.cluster_endpoint
|
cluster_endpoint = local.cluster_endpoint
|
||||||
cluster_domain = var.cluster_domain
|
cluster_domain = var.cluster_domain
|
||||||
talos_version = var.talos_version
|
talos_version = var.talos_version
|
||||||
|
kubernetes_version = var.kubernetes_version
|
||||||
|
architecture = var.talos_image_architecture
|
||||||
talos_factory_schematic_id = var.talos_factory_schematic_id
|
talos_factory_schematic_id = var.talos_factory_schematic_id
|
||||||
control_plane_vip = var.control_plane_vip
|
control_plane_vip = var.control_plane_vip
|
||||||
|
|
||||||
|
|
@ -20,34 +22,34 @@ resource "local_file" "talconfig" {
|
||||||
name = node.name,
|
name = node.name,
|
||||||
role = node.role,
|
role = node.role,
|
||||||
networks = node.networks,
|
networks = node.networks,
|
||||||
tags = node.tags,
|
tags = coalesce(node.tags, []),
|
||||||
proxmox_node = node.proxmox_node
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node)
|
||||||
} if node.role == "controlplane"]
|
} if node.role == "controlplane"]
|
||||||
|
|
||||||
compute_workers = [for node in var.nodes : {
|
compute_workers = [for node in var.nodes : {
|
||||||
name = node.name,
|
name = node.name,
|
||||||
role = node.role,
|
role = node.role,
|
||||||
networks = node.networks,
|
networks = node.networks,
|
||||||
tags = node.tags,
|
tags = coalesce(node.tags, []),
|
||||||
proxmox_node = node.proxmox_node
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node)
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "compute")]
|
} if node.role == "worker" && !contains(coalesce(node.tags, []), "storage") && !contains(coalesce(node.tags, []), "gpu")]
|
||||||
|
|
||||||
storage_workers = [for node in var.nodes : {
|
storage_workers = [for node in var.nodes : {
|
||||||
name = node.name,
|
name = node.name,
|
||||||
role = node.role,
|
role = node.role,
|
||||||
networks = node.networks,
|
networks = node.networks,
|
||||||
tags = node.tags,
|
tags = coalesce(node.tags, []),
|
||||||
proxmox_node = node.proxmox_node,
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node),
|
||||||
additional_disk_size = lookup(node, "additional_disk_size", "")
|
additional_disk_size = lookup(node, "additional_disk_size", "")
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "storage")]
|
} if node.role == "worker" && contains(coalesce(node.tags, []), "storage")]
|
||||||
|
|
||||||
gpu_workers = [for node in var.nodes : {
|
gpu_workers = [for node in var.nodes : {
|
||||||
name = node.name,
|
name = node.name,
|
||||||
role = node.role,
|
role = node.role,
|
||||||
networks = node.networks,
|
networks = node.networks,
|
||||||
tags = node.tags,
|
tags = coalesce(node.tags, []),
|
||||||
proxmox_node = node.proxmox_node
|
proxmox_node = coalesce(node.proxmox_node, var.proxmox_node)
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "gpu")]
|
} if node.role == "worker" && contains(coalesce(node.tags, []), "gpu")]
|
||||||
|
|
||||||
# Other variables
|
# Other variables
|
||||||
cluster_pod_nets = var.cluster_pod_nets
|
cluster_pod_nets = var.cluster_pod_nets
|
||||||
|
|
@ -63,16 +65,16 @@ output "node_organization" {
|
||||||
description = "How your nodes are organized for Talos configuration"
|
description = "How your nodes are organized for Talos configuration"
|
||||||
value = {
|
value = {
|
||||||
control_plane_nodes = [for node in var.nodes : {
|
control_plane_nodes = [for node in var.nodes : {
|
||||||
name = node.name, ip = node.networks[0].ip, tags = lookup(node, "tags", [])
|
name = node.name, ip = node.networks[0].ip, tags = coalesce(node.tags, [])
|
||||||
} if node.role == "controlplane"]
|
} if node.role == "controlplane"]
|
||||||
compute_workers = [for node in var.nodes : {
|
compute_workers = [for node in var.nodes : {
|
||||||
name = node.name, ip = node.networks[0].ip, tags = lookup(node, "tags", [])
|
name = node.name, ip = node.networks[0].ip, tags = coalesce(node.tags, [])
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "compute")]
|
} if node.role == "worker" && !contains(coalesce(node.tags, []), "storage") && !contains(coalesce(node.tags, []), "gpu")]
|
||||||
storage_workers = [for node in var.nodes : {
|
storage_workers = [for node in var.nodes : {
|
||||||
name = node.name, ip = node.networks[0].ip, tags = lookup(node, "tags", []), additional_disk = lookup(node, "additional_disk_size", null)
|
name = node.name, ip = node.networks[0].ip, tags = coalesce(node.tags, []), additional_disk = node.additional_disk_size
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "storage")]
|
} if node.role == "worker" && contains(coalesce(node.tags, []), "storage")]
|
||||||
gpu_workers = [for node in var.nodes : {
|
gpu_workers = [for node in var.nodes : {
|
||||||
name = node.name, ip = node.networks[0].ip, tags = lookup(node, "tags", [])
|
name = node.name, ip = node.networks[0].ip, tags = coalesce(node.tags, [])
|
||||||
} if node.role == "worker" && contains(lookup(node, "tags", []), "gpu")]
|
} if node.role == "worker" && contains(coalesce(node.tags, []), "gpu")]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ clusterName: ${cluster_name}
|
||||||
endpoint: ${cluster_endpoint}
|
endpoint: ${cluster_endpoint}
|
||||||
domain: ${cluster_domain}
|
domain: ${cluster_domain}
|
||||||
talosVersion: ${talos_version}
|
talosVersion: ${talos_version}
|
||||||
|
kubernetesVersion: "${kubernetes_version}"
|
||||||
cniConfig:
|
cniConfig:
|
||||||
name: ${cni_name}
|
name: ${cni_name}
|
||||||
allowSchedulingOnMasters: ${length(control_plane_nodes) == 1 ? "true" : "false"}
|
allowSchedulingOnMasters: ${length(control_plane_nodes) == 1 ? "true" : "false"}
|
||||||
|
|
@ -26,7 +27,7 @@ nodes:
|
||||||
installDisk: /dev/sda
|
installDisk: /dev/sda
|
||||||
machineSpec:
|
machineSpec:
|
||||||
mode: metal
|
mode: metal
|
||||||
arch: amd64
|
arch: ${architecture}
|
||||||
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
||||||
patches:
|
patches:
|
||||||
- |-
|
- |-
|
||||||
|
|
@ -70,7 +71,7 @@ nodes:
|
||||||
installDisk: /dev/sda
|
installDisk: /dev/sda
|
||||||
machineSpec:
|
machineSpec:
|
||||||
mode: metal
|
mode: metal
|
||||||
arch: amd64
|
arch: ${architecture}
|
||||||
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
||||||
patches:
|
patches:
|
||||||
- |-
|
- |-
|
||||||
|
|
@ -111,7 +112,7 @@ nodes:
|
||||||
installDisk: /dev/sda
|
installDisk: /dev/sda
|
||||||
machineSpec:
|
machineSpec:
|
||||||
mode: metal
|
mode: metal
|
||||||
arch: amd64
|
arch: ${architecture}
|
||||||
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
||||||
patches:
|
patches:
|
||||||
- |-
|
- |-
|
||||||
|
|
@ -152,7 +153,7 @@ nodes:
|
||||||
installDisk: /dev/sda
|
installDisk: /dev/sda
|
||||||
machineSpec:
|
machineSpec:
|
||||||
mode: metal
|
mode: metal
|
||||||
arch: amd64
|
arch: ${architecture}
|
||||||
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
talosImageURL: "factory.talos.dev/installer/${talos_factory_schematic_id}"
|
||||||
patches:
|
patches:
|
||||||
- |-
|
- |-
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,12 @@ variable "cni_name" {
|
||||||
default = "none"
|
default = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "kubernetes_version" {
|
||||||
|
description = "The Kubernetes version rendered into Talos configuration."
|
||||||
|
type = string
|
||||||
|
default = "v1.33.4"
|
||||||
|
}
|
||||||
|
|
||||||
# --- Global Network Configuration ---
|
# --- Global Network Configuration ---
|
||||||
variable "dns_servers" {
|
variable "dns_servers" {
|
||||||
description = "Global DNS servers for the cluster"
|
description = "Global DNS servers for the cluster"
|
||||||
|
|
@ -133,7 +139,7 @@ variable "nodes" {
|
||||||
mac_address = string
|
mac_address = string
|
||||||
cidr = string
|
cidr = string
|
||||||
vlan_id = number
|
vlan_id = number
|
||||||
ip = optional(string) # Static IP, required for control plane, optional for workers
|
ip = optional(string) # Static IP required for every rendered Talos node
|
||||||
gateway = optional(string) # Should only be defined on the primary interface
|
gateway = optional(string) # Should only be defined on the primary interface
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
@ -144,11 +150,17 @@ variable "nodes" {
|
||||||
}
|
}
|
||||||
|
|
||||||
validation {
|
validation {
|
||||||
condition = alltrue([for node in var.nodes : node.role == "controlplane" ? node.networks[0].ip != null : true])
|
condition = alltrue([for node in var.nodes : node.networks[0].ip != null])
|
||||||
error_message = "The first network for a 'controlplane' node must have a static IP address."
|
error_message = "The first network for every node must have a static IP address."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "manage_network_bridges" {
|
||||||
|
description = "Whether Terraform owns Proxmox host bridges. Leave false for shared or pre-existing bridges."
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
variable "default_physical_interface" {
|
variable "default_physical_interface" {
|
||||||
description = "Default physical interface for nodes (fallback)"
|
description = "Default physical interface for nodes (fallback)"
|
||||||
type = string
|
type = string
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue