Files
workshop/aula-08/main.tf
Allyson de Paula aa2bcfce46 aula-07 e aula-08: Cluster Talos HA na Hetzner com Autoscaler
aula-07: Criação de imagem Talos customizada na Hetzner Cloud
- Usa Talos Factory para gerar imagem ARM64/AMD64
- Inclui extensões: qemu-guest-agent, hcloud

aula-08: Provisionamento de cluster Kubernetes Talos via OpenTofu
- 3 Control Planes em HA (CAX11 ARM64)
- 1 Worker Node (CAX11 ARM64)
- Rede privada, Floating IP, Firewall
- Cluster Autoscaler para Hetzner (0-5 workers extras)
- Setup interativo com validação de pré-requisitos
- Custo estimado: ~€18/mês (base)

Também inclui:
- .gitignore para ignorar arquivos sensíveis
- CLAUDE.md com instruções do projeto
2025-12-27 07:12:58 -03:00

391 lines
11 KiB
HCL

############################################################
# 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"
}