############################################################ # Hetzner Talos Kubernetes Cluster - Base Infrastructure # Using custom Talos image created from ISO ############################################################ ############################################################ # PROVIDERS CONFIGURATION ############################################################ provider "hcloud" { token = var.hcloud_token } ############################################################ # DATA SOURCES ############################################################ # Use the custom Talos image created in aula-07 data "hcloud_image" "talos" { id = var.talos_image_id } ############################################################ # RANDOM RESOURCES ############################################################ resource "random_string" "cluster_id" { length = 6 special = false lower = true upper = false } locals { cluster_name = "talos-${random_string.cluster_id.result}" common_labels = { cluster = local.cluster_name environment = var.environment managed_by = "terraform" } } ############################################################ # SSH KEY (for emergency access only) ############################################################ data "hcloud_ssh_keys" "all" {} locals { ssh_key_normalized = trimspace(split(" ", var.ssh_public_key)[0] == "ssh-rsa" ? join(" ", slice(split(" ", var.ssh_public_key), 0, 2)) : var.ssh_public_key) ssh_key_matches = [ for key in data.hcloud_ssh_keys.all.ssh_keys : key.id if key.public_key == local.ssh_key_normalized || key.public_key == var.ssh_public_key ] ssh_key_id = length(local.ssh_key_matches) > 0 ? local.ssh_key_matches[0] : hcloud_ssh_key.admin[0].id } resource "hcloud_ssh_key" "admin" { count = length(local.ssh_key_matches) == 0 ? 1 : 0 name = "${local.cluster_name}-admin" public_key = var.ssh_public_key labels = local.common_labels } ############################################################ # NETWORK CONFIGURATION ############################################################ resource "hcloud_network" "cluster" { name = "${local.cluster_name}-network" ip_range = "10.0.0.0/16" labels = local.common_labels } resource "hcloud_network_subnet" "cluster" { type = "cloud" network_id = hcloud_network.cluster.id network_zone = "eu-central" ip_range = "10.0.1.0/24" } ############################################################ # FIREWALL CONFIGURATION ############################################################ resource "hcloud_firewall" "cluster" { name = "${local.cluster_name}-firewall" labels = local.common_labels # Talos API access rule { direction = "in" protocol = "tcp" port = "50000" source_ips = ["0.0.0.0/0", "::/0"] } # Kubernetes API rule { direction = "in" protocol = "tcp" port = "6443" source_ips = ["0.0.0.0/0", "::/0"] } # Allow HTTP/HTTPS for Ingress rule { direction = "in" protocol = "tcp" port = "80" source_ips = ["0.0.0.0/0", "::/0"] } rule { direction = "in" protocol = "tcp" port = "443" source_ips = ["0.0.0.0/0", "::/0"] } # Allow NodePort range (for services) rule { direction = "in" protocol = "tcp" port = "30000-32767" source_ips = ["0.0.0.0/0", "::/0"] } # Allow all outbound traffic rule { direction = "out" protocol = "tcp" port = "any" destination_ips = ["0.0.0.0/0", "::/0"] } rule { direction = "out" protocol = "udp" port = "any" destination_ips = ["0.0.0.0/0", "::/0"] } rule { direction = "out" protocol = "icmp" destination_ips = ["0.0.0.0/0", "::/0"] } } ############################################################ # PLACEMENT GROUP (keep nodes close for low latency) ############################################################ resource "hcloud_placement_group" "cluster" { name = "${local.cluster_name}-pg" type = "spread" labels = local.common_labels } ############################################################ # CONTROL PLANE NODES (HA with 3 CAX11 nodes) ############################################################ resource "hcloud_server" "control_plane" { count = 3 name = "${local.cluster_name}-cp-${count.index}" server_type = "cax11" image = data.hcloud_image.talos.id location = "nbg1" # CAX11 only available in Nuremberg ssh_keys = [local.ssh_key_id] firewall_ids = [hcloud_firewall.cluster.id] placement_group_id = hcloud_placement_group.cluster.id labels = merge(local.common_labels, { role = "control-plane" node = "cp-${count.index}" arch = "arm64" }) public_net { ipv4_enabled = true ipv6_enabled = true } lifecycle { ignore_changes = [ssh_keys] } } resource "hcloud_server_network" "control_plane" { count = 3 server_id = hcloud_server.control_plane[count.index].id network_id = hcloud_network.cluster.id ip = "10.0.1.${10 + count.index}" } # Floating IP for stable control plane access resource "hcloud_floating_ip" "control_plane" { type = "ipv4" name = "${local.cluster_name}-cp-ip" home_location = "nbg1" labels = local.common_labels } resource "hcloud_floating_ip_assignment" "control_plane" { floating_ip_id = hcloud_floating_ip.control_plane.id server_id = hcloud_server.control_plane[0].id } ############################################################ # WORKER NODE (Single CAX11) ############################################################ resource "hcloud_server" "worker" { count = 1 name = "${local.cluster_name}-worker-${count.index}" server_type = "cax11" image = data.hcloud_image.talos.id location = "nbg1" ssh_keys = [local.ssh_key_id] firewall_ids = [hcloud_firewall.cluster.id] placement_group_id = hcloud_placement_group.cluster.id labels = merge(local.common_labels, { role = "worker" node = "worker-${count.index}" arch = "arm64" }) public_net { ipv4_enabled = true ipv6_enabled = true } lifecycle { ignore_changes = [ssh_keys] } } resource "hcloud_server_network" "worker" { count = 1 server_id = hcloud_server.worker[count.index].id network_id = hcloud_network.cluster.id ip = "10.0.1.${20 + count.index}" } ############################################################ # TALOS CONFIGURATION ############################################################ # Generate Talos machine secrets resource "talos_machine_secrets" "this" { talos_version = var.talos_version } # Generate Talos client configuration data "talos_client_configuration" "this" { cluster_name = local.cluster_name client_configuration = talos_machine_secrets.this.client_configuration endpoints = [hcloud_floating_ip.control_plane.ip_address] } # Control plane configuration data "talos_machine_configuration" "control_plane" { count = 3 cluster_name = local.cluster_name machine_type = "controlplane" cluster_endpoint = "https://${hcloud_floating_ip.control_plane.ip_address}:6443" machine_secrets = talos_machine_secrets.this.machine_secrets talos_version = var.talos_version config_patches = [ templatefile("${path.module}/talos-patches/control-plane.yaml", { cluster_name = local.cluster_name node_name = hcloud_server.control_plane[count.index].name is_ha = true is_first_cp = count.index == 0 etcd_peers = [for i in range(3) : "10.0.1.${10 + i}"] floating_ip = hcloud_floating_ip.control_plane.ip_address }) ] depends_on = [ hcloud_server.control_plane, hcloud_floating_ip_assignment.control_plane ] } # Worker configuration data "talos_machine_configuration" "worker" { count = 1 cluster_name = local.cluster_name machine_type = "worker" cluster_endpoint = "https://${hcloud_floating_ip.control_plane.ip_address}:6443" machine_secrets = talos_machine_secrets.this.machine_secrets talos_version = var.talos_version config_patches = [ templatefile("${path.module}/talos-patches/worker.yaml", { cluster_name = local.cluster_name node_name = hcloud_server.worker[count.index].name }) ] depends_on = [ hcloud_server.worker, hcloud_floating_ip_assignment.control_plane ] } ############################################################ # APPLY TALOS CONFIGURATION ############################################################ resource "talos_machine_configuration_apply" "control_plane" { count = 3 client_configuration = talos_machine_secrets.this.client_configuration machine_configuration_input = data.talos_machine_configuration.control_plane[count.index].machine_configuration endpoint = hcloud_server.control_plane[count.index].ipv4_address node = hcloud_server.control_plane[count.index].ipv4_address depends_on = [ hcloud_server_network.control_plane, data.talos_machine_configuration.control_plane ] } resource "talos_machine_configuration_apply" "worker" { count = 1 client_configuration = talos_machine_secrets.this.client_configuration machine_configuration_input = data.talos_machine_configuration.worker[count.index].machine_configuration endpoint = hcloud_server.worker[count.index].ipv4_address node = hcloud_server.worker[count.index].ipv4_address depends_on = [ hcloud_server_network.worker, data.talos_machine_configuration.worker, talos_machine_configuration_apply.control_plane ] } ############################################################ # BOOTSTRAP KUBERNETES ############################################################ resource "talos_machine_bootstrap" "this" { client_configuration = talos_machine_secrets.this.client_configuration node = hcloud_server.control_plane[0].ipv4_address depends_on = [ talos_machine_configuration_apply.control_plane, talos_machine_configuration_apply.worker ] } ############################################################ # GET KUBECONFIG ############################################################ resource "talos_cluster_kubeconfig" "this" { client_configuration = talos_machine_secrets.this.client_configuration node = hcloud_server.control_plane[0].ipv4_address depends_on = [talos_machine_bootstrap.this] } ############################################################ # SAVE CONFIGURATIONS ############################################################ resource "local_sensitive_file" "kubeconfig" { # Replace the internal hostname with the floating IP for external access content = replace( talos_cluster_kubeconfig.this.kubeconfig_raw, "https://${local.cluster_name}.local:6443", "https://${hcloud_floating_ip.control_plane.ip_address}:6443" ) filename = "${path.root}/kubeconfig" } resource "local_sensitive_file" "talosconfig" { content = data.talos_client_configuration.this.talos_config filename = "${path.root}/talosconfig" }