Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
Binary file added infrastructure/.DS_Store
Binary file not shown.
37 changes: 37 additions & 0 deletions infrastructure/proxmox-for-educates/01-storage.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
resource "proxmox_virtual_environment_download_file" "os_image" {
# Logic: If datastore.shared is true, loop once. If false, loop per node used.
for_each = var.proxmox_image_datastore.shared ? (
toset([var.proxmox_nodes[0]])
) : (
toset([for n in var.kube_nodes : var.proxmox_nodes[n.proxmox_host]])
)
content_type = "iso"
datastore_id = var.proxmox_image_datastore.name # Use the 'name' property
node_name = each.value
url = var.cloud_image_url
file_name = "ubuntu-24.04-cloud.img"
}

resource "proxmox_virtual_environment_file" "k3s_cloud_config" {
# Logic: If flavor is Talos, we create 0 snippets.
# If it's single-node (k3s), we create snippets for the nodes.
for_each = var.deployment_flavor == "single-node" ? var.kube_nodes : {}
content_type = "snippets"
datastore_id = var.proxmox_image_datastore.name
# Logic: If your storage is shared, we upload via the first node.
# If it is local, we upload to the specific node where the VM will live.
node_name = var.proxmox_image_datastore.shared ? (
var.proxmox_nodes[0]
) : (
var.proxmox_nodes[each.value.proxmox_host]
)
source_raw {
data = templatefile("${path.module}/templates/k3s-init.tftpl", {
hostname = each.key
# This adds 6 spaces to the start of every line in your config_patches list
extra_configs = join("\n ", each.value.config_patches)
})
# This names the file on the Proxmox storage (e.g., k3s-init-educates-01.yaml)
file_name = "k3s-init-${each.key}.yaml"
}
}
58 changes: 58 additions & 0 deletions infrastructure/proxmox-for-educates/10-compute.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
resource "proxmox_virtual_environment_vm" "kube_node" {
# We use the flavor variable to control which nodes are created
for_each = var.kube_nodes

name = each.key
node_name = var.proxmox_nodes[each.value.proxmox_host]
agent { enabled = true }

cpu {
cores = each.value.vm_cores
# Using 'host' allows nested virtualization/better performance
# but requires identical CPUs in the cluster.
type = "x86-64-v2-AES"
}

memory { dedicated = each.value.vm_memory }

disk {
datastore_id = var.proxmox_vm_datastore.name

file_id = var.proxmox_image_datastore.shared ? (
proxmox_virtual_environment_download_file.os_image[var.proxmox_nodes[0]].id
) : (
proxmox_virtual_environment_download_file.os_image[var.proxmox_nodes[each.value.proxmox_host]].id
)

interface = "virtio0"
iothread = true
discard = "on"
size = each.value.vm_disk_size
}

network_device {
mac_address = lookup(each.value, "mac_address", null)
bridge = var.proxmox_network_bridge
}

initialization {
datastore_id = var.proxmox_image_datastore.name

# MODIFICATION: Only use the snippet if the flavor is single-node/k3s
user_data_file_id = var.deployment_flavor == "single-node" ? (
proxmox_virtual_environment_file.k3s_cloud_config[each.key].id
) : null

ip_config {
ipv4 {
address = "${each.value.ip_address}/24"
gateway = "192.168.80.1"
}
}

user_account {
username = var.vm_user
keys = [trimspace(file(var.ssh_key_path))]
}
}
}
117 changes: 117 additions & 0 deletions infrastructure/proxmox-for-educates/platform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
locals {
single_node_ip = [for n in var.kube_nodes : n.ip_address if n.type == "single-node"][0]
}

# --- TLS GENERATION ---
resource "tls_private_key" "educates_key" {
algorithm = "RSA"
rsa_bits = 2048
}

resource "tls_self_signed_cert" "educates_cert" {
private_key_pem = tls_private_key.educates_key.private_key_pem
subject {
common_name = "*.${var.portal_domain}"
organization = "Educates Lab"
}
validity_period_hours = 8760
allowed_uses = ["key_encipherment", "digital_signature", "server_auth"]
}

# --- PROVIDERS ---
provider "kubernetes" {
host = "https://${local.single_node_ip}:6443"
insecure = true
}

provider "kubectl" {
host = "https://${local.single_node_ip}:6443"
insecure = true
}

# --- WAIT LOGIC ---
resource "null_resource" "wait_for_k3s" {
provisioner "remote-exec" {
connection {
type = "ssh"
user = var.vm_user
host = local.single_node_ip
private_key = file("~/.ssh/id_rsa")
}
inline = [
"until [ -f /etc/rancher/k3s/k3s.yaml ]; do sleep 5; done",
"echo 'K3s Ready'"
]
}
depends_on = [proxmox_virtual_environment_vm.kube_node]
}

# --- ENGINE: KAPP-CONTROLLER ---
data "http" "kapp_controller_manifest" {
url = "https://github.com/carvel-dev/kapp-controller/releases/latest/download/release.yaml"
}

data "kubectl_file_documents" "kapp_controller" {
content = data.http.kapp_controller_manifest.response_body
}

resource "kubectl_manifest" "kapp_controller" {
for_each = data.kubectl_file_documents.kapp_controller.manifests
yaml_body = each.value
depends_on = [null_resource.wait_for_k3s]
}

# --- EDUCATES RESOURCES ---
resource "kubectl_manifest" "educates_namespace" {
yaml_body = "apiVersion: v1\nkind: Namespace\nmetadata:\n name: educates-installer"
depends_on = [null_resource.wait_for_k3s]
}

resource "kubernetes_secret" "educates_tls" {
metadata {
name = "educates-der-tls"
namespace = "educates-installer"
}
type = "kubernetes.io/tls"
data = {
"tls.crt" = tls_self_signed_cert.educates_cert.cert_pem
"tls.key" = tls_private_key.educates_key.private_key_pem
}
depends_on = [kubectl_manifest.educates_namespace]
}

resource "kubernetes_secret" "educates_config" {
metadata {
name = "educates-config"
namespace = "educates-installer"
}
data = {
"values.yaml" = <<-YAML
clusterDomain: ${var.portal_domain}
ingress:
enabled: true
className: traefik
trainingPortal:
ingress:
tls:
secretName: educates-der-tls
YAML
}
depends_on = [kubectl_manifest.educates_namespace]
}

resource "kubectl_manifest" "educates_installer_app" {
yaml_body = <<-YAML
apiVersion: kappctrl.k14s.io/v1alpha1
kind: App
metadata:
name: installer.educates.dev
namespace: educates-installer
spec:
serviceAccountName: educates-installer
fetch:
- imgpkgBundle:
image: ghcr.io/educates/educates-installer:${var.educates_version}
YAML
depends_on = [kubectl_manifest.kapp_controller, kubernetes_secret.educates_config]
}
8 changes: 8 additions & 0 deletions infrastructure/proxmox-for-educates/templates/k3s-init.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#cloud-config
hostname: ${hostname}
write_files:
- path: /etc/rancher/k3s/config.yaml.d/node-custom.yaml
content: |
${extra_configs}
runcmd:
- curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --write-kubeconfig-mode 644" sh -
39 changes: 39 additions & 0 deletions infrastructure/proxmox-for-educates/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
deployment_flavor = "single-node"

# Connection & Auth
proxmox_nodes = ["pve01", "pve02"] # Array begins in position 0
proxmox_endpoint = "https://192.168.80.200:8006/"
proxmox_api_token = "terraform-user@pve!TF-Token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
proxmox_node = "proxmox-server"

# Storage & Images
proxmox_image_datastore = {
name = "local"
shared = false
}
proxmox_vm_datastore = {
name = "data-vms"
shared = false
}

cloud_image_url = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"

# SSH Paths
ssh_key_path = "~/.ssh/id_ed25519.pub"
ssh_private_key_path = "~/.ssh/id_ed25519"

# Your Single-Node Definition
kube_nodes = {
"educates-single-node-01" = {
type = "single-node"
proxmox_host = 0
ip_address = "192.168.80.201"
#mac_address = "BC:24:11:AA:BB:CC"
vm_cores = 4
vm_memory = 8192
config_patches = [
"node-label: flavor=single-node"
# ServiceLB is ENABLED by default in k3s, so we leave it alone here.
]
}
}
116 changes: 116 additions & 0 deletions infrastructure/proxmox-for-educates/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
variable "deployment_flavor" {
type = string
description = "The type of deployment: 'single-node', 'k3s-cluster', or 'talos-cluster'"
validation {
condition = contains(["single-node", "k3s-cluster", "talos-cluster"], var.deployment_flavor)
error_message = "Flavor must be one of: single-node, k3s-cluster, talos-cluster."
}
}

variable "proxmox_endpoint" {
type = string
default = "https://192.168.1.28:8006"
}

variable "proxmox_api_token" {
type = string
sensitive = true # This hides the token in your logs
}

variable "proxmox_nodes" {
type = list(string)
default = ["proxmox-server"]
}

variable "proxmox_node" {
type = string
default = "proxmox-server"
}

variable "ssh_private_key_path" {
description = "Path to the SSH private key to connect to Proxmox"
type = string
default = "~/.ssh/id_ed25519"
}

variable "proxmox_image_datastore" {
description = "Storage for ISOs and Snippets (ej: local)"
type = object({
name = string
shared = bool
})
default = {
name = "local"
shared = false
}
}

variable "proxmox_vm_datastore" {
description = "Storage for VMs Disks (ej: data-vms)"
type = object({
name = string
shared = bool
})
validation {
# If flavor is NOT single-node, shared MUST be true.
condition = var.deployment_flavor == "single-node" || var.proxmox_vm_datastore.shared == true
error_message = "CRITICAL: For cluster deployments, the VM datastore MUST be shared (NFS/Ceph) to ensure HA and data persistence across nodes."
}
}

variable "cloud_image_url" {
type = string
default = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
}

variable "proxmox_image_datastore" {
type = string
default = "data-vms"
}

variable "proxmox_network_bridge" {
type = string
default = "vmbr0"
}

variable "vm_user" {
type = string
default = "ubuntu" # Standard for the Ubuntu image we are using
}

variable "vm_password" {
description = "Contraseña para el usuario 'debian' en las VMs"
type = string
sensitive = true # Prevents it from being displayed in plain text in the terminal/logs
}

variable "ssh_key_path" {
description = "Local path to the SSH public key to inject into the VMs"
type = string
default = "~/.ssh/id_ed25519.pub"
}

variable "portal_domain" {
type = string
default = "educates.lab.inet"
}


variable "kube_nodes" {
description = "Unified node configuration"
type = map(object({
type = string
proxmox_host = number
ip_address = string
mac_address = string
vm_cores = optional(number, 8)
vm_memory = optional(number, 16384)
vm_disk_size = optional(number, 50)
config_patches = optional(list(string), [])
}))
}

variable "educates_version" {
type = string
default = "latest"
}
10 changes: 10 additions & 0 deletions infrastructure/proxmox-for-educates/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.14.8"

required_providers {
proxmox = {
source = "bpg/proxmox"
version = ">= 0.102.0"
}
}
}
Binary file added root-modules/.DS_Store
Binary file not shown.
Loading