Skip to content
Merged
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
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,64 @@ module "talos" {
}
```

### Mixed Worker Node Types

For more advanced use cases, you can define different types of worker nodes with individual configurations using the `worker_nodes` variable:

```hcl
module "talos" {
source = "hcloud-talos/talos/hcloud"
version = "<latest-version>"

talos_version = "v1.10.3"
kubernetes_version = "1.30.3"

hcloud_token = "your-hcloud-token"
firewall_use_current_ip = true

cluster_name = "mixed-cluster"
datacenter_name = "fsn1-dc14"

control_plane_count = 1
control_plane_server_type = "cx22"

# Define different worker node types
worker_nodes = [
# Standard x86 workers
{
type = "cx22"
labels = {
"node.kubernetes.io/instance-type" = "cx22"
}
},
# ARM workers for specific workloads with taints
{
type = "cax22"
labels = {
"node.kubernetes.io/arch" = "arm64"
"affinity.example.com" = "example"
}
taints = [
{
key = "arm64-only"
value = "true"
effect = "NoSchedule"
}
]
}
]
}
```

> [!NOTE]
> The `worker_nodes` variable allows you to:
> - Mix different server types (x86 and ARM)
> - Add custom labels to nodes
> - Apply taints for workload isolation
> - Control the count of each node type independently
>
> The legacy `worker_count` and `worker_server_type` variables are still supported for backward compatibility but are deprecated in favor of `worker_nodes`.

You need to pipe the outputs of the module:

```hcl
Expand Down
6 changes: 3 additions & 3 deletions network.tf
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ resource "hcloud_primary_ip" "control_plane_ipv6" {
}

resource "hcloud_primary_ip" "worker_ipv4" {
count = var.worker_count
count = local.total_worker_count
name = "${local.cluster_prefix}worker-${count.index + 1}-ipv4"
datacenter = data.hcloud_datacenter.this.name
type = "ipv4"
Expand All @@ -97,7 +97,7 @@ resource "hcloud_primary_ip" "worker_ipv4" {
}

resource "hcloud_primary_ip" "worker_ipv6" {
count = var.enable_ipv6 ? var.worker_count > 0 ? var.worker_count : 1 : 0
count = var.enable_ipv6 ? local.total_worker_count > 0 ? local.total_worker_count : 1 : 0
name = "${local.cluster_prefix}worker-${count.index + 1}-ipv6"
datacenter = data.hcloud_datacenter.this.name
type = "ipv6"
Expand Down Expand Up @@ -139,6 +139,6 @@ locals {
for index in range(var.control_plane_count > 0 ? var.control_plane_count : 1) : cidrhost(hcloud_network_subnet.nodes.ip_range, index + 101)
]
worker_private_ipv4_list = [
for index in range(var.worker_count > 0 ? var.worker_count : 1) : cidrhost(hcloud_network_subnet.nodes.ip_range, index + 201)
for index in range(local.total_worker_count > 0 ? local.total_worker_count : 1) : cidrhost(hcloud_network_subnet.nodes.ip_range, index + 201)
]
}
10 changes: 6 additions & 4 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ output "kubeconfig" {
}

output "talos_client_configuration" {
value = data.talos_client_configuration.this
value = data.talos_client_configuration.this
sensitive = true
}

output "talos_machine_configurations_control_plane" {
Expand Down Expand Up @@ -40,8 +41,9 @@ output "hetzner_network_id" {

output "talos_worker_ids" {
description = "Server IDs of the hetzner talos workers machines"
value = {
for id, server in hcloud_server.workers : id => server.id
}
value = merge(
{ for id, server in hcloud_server.workers_new : id => server.id },
{ for id, server in hcloud_server.workers : id => server.id }
)
}

130 changes: 106 additions & 24 deletions server.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,57 @@ locals {
(var.disable_arm ? null : data.hcloud_image.arm[0].id) : // Use ARM image if not disabled
(var.disable_x86 ? null : data.hcloud_image.x86[0].id) // Use x86 image if not disabled
)
worker_image_id = (
var.worker_count > 0 ? # Only calculate if workers exist
(
substr(var.worker_server_type, 0, 3) == "cax" ?
(var.disable_arm ? null : data.hcloud_image.arm[0].id) : // Use ARM image if not disabled
(var.disable_x86 ? null : data.hcloud_image.x86[0].id) // Use x86 image if not disabled
) : null # No workers, no image needed
)

# Calculate total worker count from both old and new variables
legacy_worker_count = var.worker_count
new_worker_count = length(var.worker_nodes)
total_worker_count = local.legacy_worker_count + local.new_worker_count

# Generate worker node configurations from both old and new variables
legacy_workers = var.worker_count > 0 ? [
for i in range(var.worker_count) : {
index = i
name = "${local.cluster_prefix}worker-${i + 1}"
server_type = var.worker_server_type
image_id = (
substr(var.worker_server_type, 0, 3) == "cax" ?
(var.disable_arm ? null : data.hcloud_image.arm[0].id) :
(var.disable_x86 ? null : data.hcloud_image.x86[0].id)
)
ipv4_public = local.worker_public_ipv4_list[i]
ipv6_public = var.enable_ipv6 ? local.worker_public_ipv6_list[i] : null
ipv6_public_subnet = var.enable_ipv6 ? local.worker_public_ipv6_subnet_list[i] : null
ipv4_private = local.worker_private_ipv4_list[i]
labels = {}
taints = []
node_group_index = 0
node_in_group_index = i
}
] : []


new_workers = [
for i, worker in var.worker_nodes : {
index = local.legacy_worker_count + i
name = "${local.cluster_prefix}worker-${local.legacy_worker_count + i + 1}"
server_type = worker.type
image_id = (
substr(worker.type, 0, 3) == "cax" ?
(var.disable_arm ? null : data.hcloud_image.arm[0].id) :
(var.disable_x86 ? null : data.hcloud_image.x86[0].id)
)
ipv4_public = local.worker_public_ipv4_list[local.legacy_worker_count + i]
ipv6_public = var.enable_ipv6 ? local.worker_public_ipv6_list[local.legacy_worker_count + i] : null
ipv6_public_subnet = var.enable_ipv6 ? local.worker_public_ipv6_subnet_list[local.legacy_worker_count + i] : null
ipv4_private = local.worker_private_ipv4_list[local.legacy_worker_count + i]
labels = worker.labels
taints = worker.taints
}
]

# Combine legacy and new workers
workers = concat(local.legacy_workers, local.new_workers)

control_planes = [
for i in range(var.control_plane_count) : {
index = i
Expand All @@ -37,16 +80,6 @@ locals {
ipv4_private = local.control_plane_private_ipv4_list[i]
}
]
workers = [
for i in range(var.worker_count) : {
index = i
name = "${local.cluster_prefix}worker-${i + 1}"
ipv4_public = local.worker_public_ipv4_list[i],
ipv6_public = var.enable_ipv6 ? local.worker_public_ipv6_list[i] : null
ipv6_public_subnet = var.enable_ipv6 ? local.worker_public_ipv6_subnet_list[i] : null
ipv4_private = local.worker_private_ipv4_list[i]
}
]
}

resource "tls_private_key" "ssh_key" {
Expand Down Expand Up @@ -108,20 +141,69 @@ resource "hcloud_server" "control_planes" {
}

resource "hcloud_server" "workers" {
for_each = { for worker in local.workers : worker.name => worker }
for_each = { for worker in local.legacy_workers : worker.name => worker }
datacenter = data.hcloud_datacenter.this.name
name = each.value.name
image = local.worker_image_id
server_type = var.worker_server_type
image = each.value.image_id
server_type = each.value.server_type
user_data = data.talos_machine_configuration.worker[each.value.name].machine_configuration
ssh_keys = [hcloud_ssh_key.this.id]
placement_group_id = hcloud_placement_group.worker.id

labels = {
"cluster" = var.cluster_name,
"role" = "worker"
labels = merge({
"cluster" = var.cluster_name,
"role" = "worker"
"server_type" = each.value.server_type
}, each.value.labels)

firewall_ids = [
hcloud_firewall.this.id
]

public_net {
ipv4_enabled = true
ipv4 = hcloud_primary_ip.worker_ipv4[each.value.index].id
ipv6_enabled = var.enable_ipv6
ipv6 = var.enable_ipv6 ? hcloud_primary_ip.worker_ipv6[each.value.index].id : null
}

network {
network_id = hcloud_network_subnet.nodes.network_id
ip = each.value.ipv4_private
alias_ips = [] # fix for https://github.com/hetznercloud/terraform-provider-hcloud/issues/650
}

depends_on = [
hcloud_network_subnet.nodes,
data.talos_machine_configuration.worker
]

lifecycle {
ignore_changes = [
user_data,
image
]
}
}



resource "hcloud_server" "workers_new" {
for_each = { for worker in local.new_workers : worker.name => worker }
datacenter = data.hcloud_datacenter.this.name
name = each.value.name
image = each.value.image_id
server_type = each.value.server_type
user_data = data.talos_machine_configuration.worker[each.value.name].machine_configuration
ssh_keys = [hcloud_ssh_key.this.id]
placement_group_id = hcloud_placement_group.worker.id

labels = merge({
"cluster" = var.cluster_name,
"role" = "worker"
"server_type" = each.value.server_type
}, each.value.labels)

firewall_ids = [
hcloud_firewall.this.id
]
Expand Down
15 changes: 1 addition & 14 deletions talos.tf
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,7 @@ data "talos_machine_configuration" "dummy_control_plane" {
examples = false
}

# Dummy configuration generated when worker_count is 0 for debugging purposes
# tflint-ignore: terraform_unused_declarations
data "talos_machine_configuration" "dummy_worker" {
count = var.worker_count == 0 ? 1 : 0
talos_version = var.talos_version
cluster_name = var.cluster_name
cluster_endpoint = local.cluster_endpoint_url_internal # Uses dummy endpoint when count is 0
kubernetes_version = var.kubernetes_version
machine_type = "worker"
machine_secrets = talos_machine_secrets.this.machine_secrets
config_patches = concat([yamlencode(local.worker_yaml["dummy-worker-0"])], var.talos_worker_extra_config_patches) # Use dummy yaml + extra patches
docs = false
examples = false
}


resource "talos_machine_bootstrap" "this" {
count = var.control_plane_count > 0 ? 1 : 0
Expand Down
66 changes: 44 additions & 22 deletions talos_patch_worker.tf
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
locals {
# Define a dummy worker entry for when count is 0
dummy_workers = var.worker_count == 0 ? [{
index = 0
name = "dummy-worker-0"
ipv4_public = "0.0.0.0" # Fallback
ipv6_public = null # Fallback
ipv6_public_subnet = null # Fallback
ipv4_private = cidrhost(local.node_ipv4_cidr, 200) # Use a predictable dummy private IP
dummy_workers = local.total_worker_count == 0 ? [{
index = 0
name = "dummy-worker-0"
server_type = "cx11"
image_id = null
ipv4_public = "0.0.0.0" # Fallback
ipv6_public = null # Fallback
ipv6_public_subnet = null # Fallback
ipv4_private = cidrhost(local.node_ipv4_cidr, 200) # Use a predictable dummy private IP
labels = {}
taints = []
node_group_index = 0
node_in_group_index = 0
}] : []

# Combine real and dummy workers
# Combine real and dummy workers - always include dummy when no workers exist
# merged_workers = local.total_worker_count == 0 ? local.dummy_workers : local.workers
merged_workers = concat(local.workers, local.dummy_workers)

# Generate YAML for all (real or dummy) workers
Expand All @@ -23,20 +30,34 @@ locals {
]
}
certSANs = local.cert_SANs
kubelet = {
extraArgs = merge(
{
"cloud-provider" = "external"
"rotate-server-certificates" = true
},
var.kubelet_extra_args
)
nodeIP = {
validSubnets = [
local.node_ipv4_cidr
]
}
}
kubelet = merge(
{
extraArgs = merge(
{
"cloud-provider" = "external"
"rotate-server-certificates" = true
},
var.kubelet_extra_args
)
nodeIP = {
validSubnets = [
local.node_ipv4_cidr
]
}
},
# Add registerWithTaints if taints are defined
length(worker.taints) > 0 ? {
extraConfig = {
registerWithTaints = [
for taint in worker.taints : {
key = taint.key
value = taint.value
effect = taint.effect
}
]
}
} : {}
)
network = {
extraHostEntries = local.extra_host_entries
kubespan = {
Expand Down Expand Up @@ -70,6 +91,7 @@ locals {
"time.cloudflare.com"
]
}
nodeLabels = worker.labels
registries = var.registries
}
cluster = {
Expand Down
Loading