From 43c9cb4d961d6521e0b8bbfdbc9c0eae26b8b0c3 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:06:44 +0200 Subject: [PATCH 01/30] Update variables.tf added new variable --- variables.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variables.tf b/variables.tf index c91fc394..f4069800 100644 --- a/variables.tf +++ b/variables.tf @@ -242,6 +242,7 @@ variable "agent_nodepools" { labels = list(string) taints = list(string) longhorn_volume_size = optional(number) + longhorn_mount_path = optional(string, "/var/longhorn") swap_size = optional(string, "") zram_size = optional(string, "") kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"]) @@ -261,6 +262,7 @@ variable "agent_nodepools" { labels = optional(list(string)) taints = optional(list(string)) longhorn_volume_size = optional(number) + longhorn_mount_path = optional(string, "/var/longhorn") swap_size = optional(string, "") zram_size = optional(string, "") kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"]) From 7c15422c694c76813928dd2bc57b29bfd733df30 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:10:09 +0200 Subject: [PATCH 02/30] Update agents.tf mount path now parameterised --- agents.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agents.tf b/agents.tf index 33c39518..a65fb7da 100644 --- a/agents.tf +++ b/agents.tf @@ -192,10 +192,10 @@ resource "null_resource" "configure_longhorn_volume" { # Start the k3s agent and wait for it to have started provisioner "remote-exec" { inline = [ - "mkdir /var/longhorn >/dev/null 2>&1", - "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} /var/longhorn", + "mkdir ${var.longhorn_mount_path} >/dev/null 2>&1", + "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path}", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", - "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} /var/longhorn ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" + "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] } From 3336faf2592c4589ddab49553866d287d5578e01 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:25:41 +0200 Subject: [PATCH 03/30] Update locals.tf added new variable --- locals.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locals.tf b/locals.tf index 8ca4c293..fe8417b7 100644 --- a/locals.tf +++ b/locals.tf @@ -224,6 +224,7 @@ locals { nodepool_name : nodepool_obj.name, server_type : nodepool_obj.server_type, longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0), + longhorn_mount_path : nodepool_obj.longhorn_mount_path, floating_ip : lookup(nodepool_obj, "floating_ip", false), floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false), location : nodepool_obj.location, @@ -253,6 +254,7 @@ locals { nodepool_name : nodepool_obj.name, server_type : nodepool_obj.server_type, longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0), + longhorn_mount_path : nodepool_obj.longhorn_mount_path floating_ip : lookup(nodepool_obj, "floating_ip", false), floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false), location : nodepool_obj.location, From 574df9e6c46428a558b9f37da5517e7545d9aa54 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:31:08 +0200 Subject: [PATCH 04/30] Update agents.tf updated with suggested changes --- agents.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agents.tf b/agents.tf index a65fb7da..914ded8e 100644 --- a/agents.tf +++ b/agents.tf @@ -192,10 +192,10 @@ resource "null_resource" "configure_longhorn_volume" { # Start the k3s agent and wait for it to have started provisioner "remote-exec" { inline = [ - "mkdir ${var.longhorn_mount_path} >/dev/null 2>&1", - "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path}", - "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", - "echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${var.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" + "mkdir -p ${each.value.longhorn_mount_path} >/dev/null 2>&1", + "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path}", + "${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", + "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] } From 4f77bb5d500924019f4b70edc1e18ef8afd49f15 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:40:48 +0200 Subject: [PATCH 05/30] Update locals.tf fix syntax --- locals.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locals.tf b/locals.tf index fe8417b7..e26fe72f 100644 --- a/locals.tf +++ b/locals.tf @@ -254,7 +254,7 @@ locals { nodepool_name : nodepool_obj.name, server_type : nodepool_obj.server_type, longhorn_volume_size : coalesce(nodepool_obj.longhorn_volume_size, 0), - longhorn_mount_path : nodepool_obj.longhorn_mount_path + longhorn_mount_path : nodepool_obj.longhorn_mount_path, floating_ip : lookup(nodepool_obj, "floating_ip", false), floating_ip_rdns : lookup(nodepool_obj, "floating_ip_rdns", false), location : nodepool_obj.location, From d68210ec6164dd9e6944624548ec0d3821d657e7 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:44:40 +0200 Subject: [PATCH 06/30] Update agents.tf commit suggestions --- agents.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agents.tf b/agents.tf index 914ded8e..240dd189 100644 --- a/agents.tf +++ b/agents.tf @@ -192,8 +192,8 @@ resource "null_resource" "configure_longhorn_volume" { # Start the k3s agent and wait for it to have started provisioner "remote-exec" { inline = [ - "mkdir -p ${each.value.longhorn_mount_path} >/dev/null 2>&1", - "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path}", + "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", + "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] From 785d476e4b57d91c2b8a3527340518f6e82dc529 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:46:26 +0200 Subject: [PATCH 07/30] Update variables.tf added variable validation --- variables.tf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/variables.tf b/variables.tf index f4069800..3c8fe224 100644 --- a/variables.tf +++ b/variables.tf @@ -305,6 +305,14 @@ variable "agent_nodepools" { error_message = "Hetzner does not support networks with more than 100 servers." } + validation { + condition = ( + can(regex("^/([a-zA-Z0-9._-]+/?)*$", var.longhorn_mount_path)) && + !endswith(var.longhorn_mount_path, "/") || var.longhorn_mount_path == "/" + ) + error_message = "The longhorn_mount_path must be a valid absolute Linux path without a trailing slash (except root '/'), and contain only alphanumeric characters, dashes, underscores, and dots." + } + } variable "cluster_autoscaler_image" { From fffc5bdc1213a99d7015ab032d4a47277c24c787 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:04:10 +0200 Subject: [PATCH 08/30] Update agents.tf removed / from provisioner --- agents.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents.tf b/agents.tf index 240dd189..565067b8 100644 --- a/agents.tf +++ b/agents.tf @@ -194,7 +194,7 @@ resource "null_resource" "configure_longhorn_volume" { inline = [ "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", - "${var.longhorn_fstype == \"ext4\" ? \"resize2fs\" : \"xfs_growfs\"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", + "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] } From e4c33b3090a92c0a708f39ff0bfb822cb48da3cf Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:08:35 +0200 Subject: [PATCH 09/30] Update variables.tf fixed validation rule --- variables.tf | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/variables.tf b/variables.tf index 3c8fe224..5ae30a0c 100644 --- a/variables.tf +++ b/variables.tf @@ -306,11 +306,15 @@ variable "agent_nodepools" { } validation { - condition = ( - can(regex("^/([a-zA-Z0-9._-]+/?)*$", var.longhorn_mount_path)) && - !endswith(var.longhorn_mount_path, "/") || var.longhorn_mount_path == "/" - ) - error_message = "The longhorn_mount_path must be a valid absolute Linux path without a trailing slash (except root '/'), and contain only alphanumeric characters, dashes, underscores, and dots." + condition = alltrue([ + for np in var.agent_nodepools : ( + ( + can(regex("^/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && + (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/") + ) + ) + ]) + error_message = "Each longhorn_mount_path must be a valid absolute path without trailing slash (except '/')." } } From 8ad21417f6b92c459e6037a373a8e369ce1870f0 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:17:19 +0200 Subject: [PATCH 10/30] Update variables.tf updated validation rule for longhorn --- variables.tf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/variables.tf b/variables.tf index 5ae30a0c..92a6275c 100644 --- a/variables.tf +++ b/variables.tf @@ -306,15 +306,15 @@ variable "agent_nodepools" { } validation { - condition = alltrue([ - for np in var.agent_nodepools : ( - ( - can(regex("^/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && - (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/") - ) + condition = alltrue([ + for np in var.agent_nodepools : ( + ( + can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && + (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/") ) - ]) - error_message = "Each longhorn_mount_path must be a valid absolute path without trailing slash (except '/')." + ) + ]) + error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except '/var/')." } } From 94b77e6334fd792f4dd1915e993b2f57b0328b22 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:11:22 +0200 Subject: [PATCH 11/30] Create customize-mount-path-longhorn.md added new doc --- docs/customize-mount-path-longhorn.md | 97 +++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/customize-mount-path-longhorn.md diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md new file mode 100644 index 00000000..1d204411 --- /dev/null +++ b/docs/customize-mount-path-longhorn.md @@ -0,0 +1,97 @@ +## Page describes how to use longhorn custom mount path +
+In order to use nvme and extarnal disks with longhorn, you need to mount extarnal disk to another location under `/var/` folder. +This gives more storage capacity across cluster, if you didint disable defualt longhorn disks off course. + + +> ⚠️ Note: You can set any mount path but only within `/var/` folder + +### How set mount for your external disk differs to ``/var/`` folder ? + +1. You must anable longhorn in you module +2. Set helm values +```yamllonghorn_values = < node.metadata[0].name + } + triggers = { + always_run = timestamp() + } + provisioner "local-exec" { + command = <<-EOT + KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{ + "spec": { + "disks": { + "external-ssd": { + "path": "/var/lib/longhorn", # Path you set in nodepools variable + "allowScheduling": true, + "tags": ["ssd"] + } + } + } + }' + EOT + } +} + +resource "kubernetes_manifest" "longhorn_ssd_replica" { + manifest = { + apiVersion = "storage.k8s.io/v1" + kind = "StorageClass" + metadata = { + name = "longhorn-ssd-replica" + } + provisioner = "driver.longhorn.io" + parameters = { + numberOfReplicas = "1" + staleReplicaTimeout = "30" + diskSelector = "ssd" + + fromBackup = "" + } + reclaimPolicy = "Delete" + allowVolumeExpansion = true + volumeBindingMode = "Immediate" + } + depends_on = [null_resource.longhorn_patch_external_disk] +} +``` From 0ae30f09b936521346048636bcc00465d665bbeb Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:50:30 +0200 Subject: [PATCH 12/30] Update kube.tf.example added small description to kube.tf.example --- kube.tf.example | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kube.tf.example b/kube.tf.example index 7068e54f..e50d4b5c 100644 --- a/kube.tf.example +++ b/kube.tf.example @@ -233,7 +233,9 @@ module "kube-hetzner" { # Something worth noting is that Volume storage is slower than node storage, which is achieved by not mentioning longhorn_volume_size or setting it to 0. # So for something like DBs, you definitely want node storage, for other things like backups, volume storage is fine, and cheaper. # longhorn_volume_size = 20 - + # Set any path inside /var/ folder to have ability use additional storage class along with default one which is by default must be set in helm values to + # /var/longhorn + # longhorn_mount_path = "/var/lib/longhorn" # Enable automatic backups via Hetzner (default: false) # backups = true }, From 54af473a4e7ce793adbf6bc27a58e0c86474cd0e Mon Sep 17 00:00:00 2001 From: garry-t Date: Fri, 10 Oct 2025 14:37:28 +0200 Subject: [PATCH 13/30] - fix typo --- docs/customize-mount-path-longhorn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index 1d204411..d47cbac0 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -8,7 +8,7 @@ This gives more storage capacity across cluster, if you didint disable defualt l ### How set mount for your external disk differs to ``/var/`` folder ? -1. You must anable longhorn in you module +1. You must enable longhorn in you module 2. Set helm values ```yamllonghorn_values = < Date: Sat, 11 Oct 2025 13:37:38 +0200 Subject: [PATCH 14/30] - gemini suggest fix doc --- docs/customize-mount-path-longhorn.md | 185 ++++++++++++++------------ 1 file changed, 98 insertions(+), 87 deletions(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index d47cbac0..538897e9 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -1,97 +1,108 @@ -## Page describes how to use longhorn custom mount path +## How to use a custom mount path for Longhorn
-In order to use nvme and extarnal disks with longhorn, you need to mount extarnal disk to another location under `/var/` folder. -This gives more storage capacity across cluster, if you didint disable defualt longhorn disks off course. - - -> ⚠️ Note: You can set any mount path but only within `/var/` folder - -### How set mount for your external disk differs to ``/var/`` folder ? - -1. You must enable longhorn in you module -2. Set helm values -```yamllonghorn_values = < ⚠️ Note: You can set any mount path, but it must be within the `/var/` folder. + +### How to set a custom mount path for your external disk? + +1. You must enable Longhorn in your module. + ```terraform + enable_longhorn = true + ``` + +2. Set the Helm values for Longhorn. The `defaultDataPath` is important as this path is automatically created by Longhorn and will be the default storage class pointing to your primary disks (e.g., NVMe). + ```yaml + longhorn_values = < node.metadata[0].name - } - triggers = { - always_run = timestamp() - } - provisioner "local-exec" { - command = <<-EOT - KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{ - "spec": { - "disks": { - "external-ssd": { - "path": "/var/lib/longhorn", # Path you set in nodepools variable - "allowScheduling": true, - "tags": ["ssd"] - } - } - } - }' - EOT - } -} + for_each = { + for node in data.kubernetes_nodes.ssd_nodes.nodes : node.metadata[0].name => node.metadata[0].name + } -resource "kubernetes_manifest" "longhorn_ssd_replica" { - manifest = { - apiVersion = "storage.k8s.io/v1" - kind = "StorageClass" - metadata = { - name = "longhorn-ssd-replica" - } - provisioner = "driver.longhorn.io" - parameters = { - numberOfReplicas = "1" - staleReplicaTimeout = "30" - diskSelector = "ssd" - - fromBackup = "" - } - reclaimPolicy = "Delete" - allowVolumeExpansion = true - volumeBindingMode = "Immediate" - } - depends_on = [null_resource.longhorn_patch_external_disk] + triggers = { + always_run = timestamp() + } + + provisioner "local-exec" { + command = <<-EOT + KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{ + "spec": { + "disks": { + "external-ssd": { + "path": "/var/lib/longhorn", # The path you set in the nodepools variable + "allowScheduling": true, + "tags": ["ssd"] + } + } + } + }' + EOT + } } -``` + +# Create a new StorageClass for the SSD-backed Longhorn storage +resource "kubernetes_manifest" "longhorn_ssd_storageclass" { + manifest = { + apiVersion = "storage.k8s.io/v1" + kind = "StorageClass" + metadata = { + name = "longhorn-ssd" + } + provisioner = "driver.longhorn.io" + parameters = { + numberOfReplicas = "1" + staleReplicaTimeout = "30" + diskSelector = "ssd" + fromBackup = "" + } + reclaimPolicy = "Delete" + allowVolumeExpansion = true + volumeBindingMode = "Immediate" + } + + depends_on = [null_resource.longhorn_patch_external_disk] +} \ No newline at end of file From 968772bdb5060702096bd3810062b5e7788f3b7f Mon Sep 17 00:00:00 2001 From: garry-t Date: Sat, 11 Oct 2025 13:39:14 +0200 Subject: [PATCH 15/30] - gemini suggest fix comment --- kube.tf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kube.tf.example b/kube.tf.example index e50d4b5c..9ec42726 100644 --- a/kube.tf.example +++ b/kube.tf.example @@ -233,7 +233,7 @@ module "kube-hetzner" { # Something worth noting is that Volume storage is slower than node storage, which is achieved by not mentioning longhorn_volume_size or setting it to 0. # So for something like DBs, you definitely want node storage, for other things like backups, volume storage is fine, and cheaper. # longhorn_volume_size = 20 - # Set any path inside /var/ folder to have ability use additional storage class along with default one which is by default must be set in helm values to + # Set any path inside /var/ folder to have the ability to use an additional storage class along with the default one, which by default must be set in helm values to # /var/longhorn # longhorn_mount_path = "/var/lib/longhorn" # Enable automatic backups via Hetzner (default: false) From e720ea9683d8d0c92abc0c9fbb6a98da9242f349 Mon Sep 17 00:00:00 2001 From: garry-t Date: Sat, 11 Oct 2025 13:46:22 +0200 Subject: [PATCH 16/30] - gemini fix validation --- variables.tf | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/variables.tf b/variables.tf index 92a6275c..9876a1ca 100644 --- a/variables.tf +++ b/variables.tf @@ -306,15 +306,25 @@ variable "agent_nodepools" { } validation { - condition = alltrue([ - for np in var.agent_nodepools : ( - ( - can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && - (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/") + condition = alltrue(flatten([ + for np in var.agent_nodepools : concat( + [ + ( + can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && + (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/") + ) + ], + [ + for node in values(coalesce(np.nodes, {})) : ( + ( + can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", node.longhorn_mount_path)) && + (!endswith(node.longhorn_mount_path, "/") || node.longhorn_mount_path == "/var/") + ) + ) + ] ) - ) - ]) - error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except '/var/')." + ])) + error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings." } } From cb6f68cbb7d3fb9c70e672f5d9c49ce280374b62 Mon Sep 17 00:00:00 2001 From: garry-t Date: Sat, 11 Oct 2025 17:06:32 +0200 Subject: [PATCH 17/30] - gemini yet another fixes --- variables.tf | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/variables.tf b/variables.tf index 9876a1ca..e3a884d7 100644 --- a/variables.tf +++ b/variables.tf @@ -262,7 +262,7 @@ variable "agent_nodepools" { labels = optional(list(string)) taints = optional(list(string)) longhorn_volume_size = optional(number) - longhorn_mount_path = optional(string, "/var/longhorn") + longhorn_mount_path = optional(string, null) swap_size = optional(string, "") zram_size = optional(string, "") kubelet_args = optional(list(string), ["kube-reserved=cpu=50m,memory=300Mi,ephemeral-storage=1Gi", "system-reserved=cpu=250m,memory=300Mi"]) @@ -309,17 +309,11 @@ variable "agent_nodepools" { condition = alltrue(flatten([ for np in var.agent_nodepools : concat( [ - ( - can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", np.longhorn_mount_path)) && - (!endswith(np.longhorn_mount_path, "/") || np.longhorn_mount_path == "/var/") - ) + can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) ], [ for node in values(coalesce(np.nodes, {})) : ( - ( - can(regex("^/var/([a-zA-Z0-9._-]+/?)*$", node.longhorn_mount_path)) && - (!endswith(node.longhorn_mount_path, "/") || node.longhorn_mount_path == "/var/") - ) + node.longhorn_mount_path == null || can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) ) ] ) From 66d9676dcae4a18bdcde4032ebf6469ac6577cdf Mon Sep 17 00:00:00 2001 From: garry-t Date: Sat, 11 Oct 2025 17:07:41 +0200 Subject: [PATCH 18/30] - gemini mountpoint checks --- agents.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents.tf b/agents.tf index 565067b8..fc488d19 100644 --- a/agents.tf +++ b/agents.tf @@ -193,7 +193,7 @@ resource "null_resource" "configure_longhorn_volume" { provisioner "remote-exec" { inline = [ "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", - "mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", + "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] From aa01c5df4d62652c54f09ca141cdc0d04f857b5e Mon Sep 17 00:00:00 2001 From: garry-t Date: Fri, 24 Oct 2025 11:17:19 +0200 Subject: [PATCH 19/30] - gemini added "set -e" --- agents.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/agents.tf b/agents.tf index fc488d19..ab7612b5 100644 --- a/agents.tf +++ b/agents.tf @@ -192,6 +192,7 @@ resource "null_resource" "configure_longhorn_volume" { # Start the k3s agent and wait for it to have started provisioner "remote-exec" { inline = [ + "set -e", "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", From 3b517e2ac8f2cdef30d24e13bc6b991322b22983 Mon Sep 17 00:00:00 2001 From: garry-t Date: Mon, 27 Oct 2025 20:20:05 +0100 Subject: [PATCH 20/30] - gemini fix review --- agents.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents.tf b/agents.tf index ab7612b5..af5b0902 100644 --- a/agents.tf +++ b/agents.tf @@ -196,7 +196,7 @@ resource "null_resource" "configure_longhorn_volume" { "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", - "grep -qF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" + "awk '{print $2}' /etc/fstab | grep -qxF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" ] } From 889ae77736160a275d9819ff5d79b3fa7fcd03c8 Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 09:03:21 +0100 Subject: [PATCH 21/30] - provide idempotent mount command --- agents.tf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/agents.tf b/agents.tf index af5b0902..066b58a9 100644 --- a/agents.tf +++ b/agents.tf @@ -196,8 +196,9 @@ resource "null_resource" "configure_longhorn_volume" { "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", - "awk '{print $2}' /etc/fstab | grep -qxF '${each.value.longhorn_mount_path}' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' >> /etc/fstab" - ] + # Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column. + # This prevents false positives like /data matching /data1. + "grep -qE '^[^#[:space:]]+[[:space:]]+${each.value.longhorn_mount_path}[[:space:]]' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null"] } connection { From 42a32cd5fbc71f3f3b9e240353307a070040c214 Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 09:31:30 +0100 Subject: [PATCH 22/30] - fix one more gemini review comment --- agents.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents.tf b/agents.tf index 066b58a9..3b4f8b7a 100644 --- a/agents.tf +++ b/agents.tf @@ -193,7 +193,7 @@ resource "null_resource" "configure_longhorn_volume" { provisioner "remote-exec" { inline = [ "set -e", - "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null 2>&1", + "mkdir -p '${each.value.longhorn_mount_path}' >/dev/null", "mountpoint -q '${each.value.longhorn_mount_path}' || mount -o discard,defaults ${hcloud_volume.longhorn_volume[each.key].linux_device} '${each.value.longhorn_mount_path}'", "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", # Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column. From 436224a13abd8965cdcc6d5a885a40d48ac2ca9f Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 10:34:56 +0100 Subject: [PATCH 23/30] - improved regex one more time --- agents.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agents.tf b/agents.tf index 3b4f8b7a..5c5c52bc 100644 --- a/agents.tf +++ b/agents.tf @@ -198,7 +198,8 @@ resource "null_resource" "configure_longhorn_volume" { "${var.longhorn_fstype == "ext4" ? "resize2fs" : "xfs_growfs"} ${hcloud_volume.longhorn_volume[each.key].linux_device}", # Match any non-comment line (^[^#]) with any first field, followed by a space and your mount path in the second column. # This prevents false positives like /data matching /data1. - "grep -qE '^[^#[:space:]]+[[:space:]]+${each.value.longhorn_mount_path}[[:space:]]' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null"] + "awk -v path='${each.value.longhorn_mount_path}' '$0 !~ /^#/ && $2 == path { found=1; exit } END { exit !found }' /etc/fstab || echo '${hcloud_volume.longhorn_volume[each.key].linux_device} ${each.value.longhorn_mount_path} ${var.longhorn_fstype} discard,nofail,defaults 0 0' | tee -a /etc/fstab >/dev/null" + ] } connection { From b57b8190045d78fa1f3197d4409f88690edc94a0 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:13:38 +0100 Subject: [PATCH 24/30] Update docs/customize-mount-path-longhorn.md just to make gemini calm Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/customize-mount-path-longhorn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index 538897e9..c62137a7 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -94,7 +94,7 @@ resource "kubernetes_manifest" "longhorn_ssd_storageclass" { } provisioner = "driver.longhorn.io" parameters = { - numberOfReplicas = "1" + numberOfReplicas = "3" staleReplicaTimeout = "30" diskSelector = "ssd" fromBackup = "" From 8fdce2641860996e976509b6fbac7426d31462a1 Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 14:28:08 +0100 Subject: [PATCH 25/30] - improve validation variable --- variables.tf | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/variables.tf b/variables.tf index bf7bbd34..84d54558 100644 --- a/variables.tf +++ b/variables.tf @@ -309,16 +309,22 @@ variable "agent_nodepools" { condition = alltrue(flatten([ for np in var.agent_nodepools : concat( [ - can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) + can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) && + !contains(split("/", np.longhorn_mount_path), "..") && + !contains(split("/", np.longhorn_mount_path), ".") ], [ for node in values(coalesce(np.nodes, {})) : ( - node.longhorn_mount_path == null || can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) + node.longhorn_mount_path == null || ( + can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) && + !contains(split("/", node.longhorn_mount_path), "..") && + !contains(split("/", node.longhorn_mount_path), ".") + ) ) ] ) ])) - error_message = "Each longhorn_mount_path must start with '/var/', be a valid absolute path, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings." + error_message = "Each longhorn_mount_path must be a valid, absolute path starting with '/var/', not contain '.' or '..' components, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings." } } From a9dcac186b7fa01c75a3719f742ccddcc4e366db Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 14:31:39 +0100 Subject: [PATCH 26/30] - satisfy gemini --- docs/customize-mount-path-longhorn.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index c62137a7..78421166 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -62,11 +62,6 @@ resource "null_resource" "longhorn_patch_external_disk" { for_each = { for node in data.kubernetes_nodes.ssd_nodes.nodes : node.metadata[0].name => node.metadata[0].name } - - triggers = { - always_run = timestamp() - } - provisioner "local-exec" { command = <<-EOT KUBECONFIG=${var.kubeconfig_path} kubectl -n longhorn-system patch nodes.longhorn.io ${each.key} --type merge -p '{ From fe22dbd94c32d2550aaa9a8ed8bcce6f97bcd959 Mon Sep 17 00:00:00 2001 From: IT <113095009+garry-t@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:02:55 +0100 Subject: [PATCH 27/30] Update docs/customize-mount-path-longhorn.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/customize-mount-path-longhorn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index 78421166..c9e4cd75 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -68,7 +68,7 @@ resource "null_resource" "longhorn_patch_external_disk" { "spec": { "disks": { "external-ssd": { - "path": "/var/lib/longhorn", # The path you set in the nodepools variable + "path": "/var/lib/longhorn", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label. This example assumes all selected nodes use the same path. "allowScheduling": true, "tags": ["ssd"] } From 0d58cedef46324e295396d9a7e32af590eb91e24 Mon Sep 17 00:00:00 2001 From: garry-t Date: Tue, 28 Oct 2025 15:07:54 +0100 Subject: [PATCH 28/30] - gemini yet another fix --- variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variables.tf b/variables.tf index 84d54558..4a181aa7 100644 --- a/variables.tf +++ b/variables.tf @@ -309,14 +309,14 @@ variable "agent_nodepools" { condition = alltrue(flatten([ for np in var.agent_nodepools : concat( [ - can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path)) && + regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path) && !contains(split("/", np.longhorn_mount_path), "..") && !contains(split("/", np.longhorn_mount_path), ".") ], [ for node in values(coalesce(np.nodes, {})) : ( node.longhorn_mount_path == null || ( - can(regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path)) && + regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path) && !contains(split("/", node.longhorn_mount_path), "..") && !contains(split("/", node.longhorn_mount_path), ".") ) From 115988642f59f63b905ae04f7c46eb1e0a0b7569 Mon Sep 17 00:00:00 2001 From: garry-t Date: Wed, 29 Oct 2025 13:09:09 +0100 Subject: [PATCH 29/30] - gemini yet another validation fix --- variables.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variables.tf b/variables.tf index 4a181aa7..1277291f 100644 --- a/variables.tf +++ b/variables.tf @@ -309,14 +309,14 @@ variable "agent_nodepools" { condition = alltrue(flatten([ for np in var.agent_nodepools : concat( [ - regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", np.longhorn_mount_path) && + regex("^/var/[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*$", np.longhorn_mount_path) && !contains(split("/", np.longhorn_mount_path), "..") && !contains(split("/", np.longhorn_mount_path), ".") ], [ for node in values(coalesce(np.nodes, {})) : ( node.longhorn_mount_path == null || ( - regex("^/var/$|^/var/([a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*)$", node.longhorn_mount_path) && + regex("^/var/[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*$", node.longhorn_mount_path) && !contains(split("/", node.longhorn_mount_path), "..") && !contains(split("/", node.longhorn_mount_path), ".") ) @@ -324,7 +324,7 @@ variable "agent_nodepools" { ] ) ])) - error_message = "Each longhorn_mount_path must be a valid, absolute path starting with '/var/', not contain '.' or '..' components, and not end with a slash (except for '/var/'). This applies to both nodepool-level and node-level settings." + error_message = "Each longhorn_mount_path must be a valid, absolute path within a subdirectory of '/var/', not contain '.' or '..' components, and not end with a slash. This applies to both nodepool-level and node-level settings." } } From 2befe18ee12c0b2af486f3a268ff5e4ba84fbd7c Mon Sep 17 00:00:00 2001 From: garry-t Date: Wed, 29 Oct 2025 13:12:22 +0100 Subject: [PATCH 30/30] - gemini fix doc.. --- docs/customize-mount-path-longhorn.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/customize-mount-path-longhorn.md b/docs/customize-mount-path-longhorn.md index c9e4cd75..b4e2aedb 100644 --- a/docs/customize-mount-path-longhorn.md +++ b/docs/customize-mount-path-longhorn.md @@ -25,19 +25,24 @@ In order to use NVMe and external disks with Longhorn, you may need to mount an EOT ``` -3. In the `agent_nodepools` where you want to have a customized mount path, set the `longhorn_mount_path` variable. +3. In the `agent_nodepools` where you want to have a customized mount path, set the `longhorn_mount_path` variable. It's a good practice to define this path as a local variable to ensure consistency. + ```terraform + locals { + custom_longhorn_path = "/var/lib/longhorn" + } + agent_nodepools = [ { # ... other nodepool configuration labels = ["role=monitoring", "storage=ssd"], # Label we use to filter nodes longhorn_volume_size = 50, - longhorn_mount_path = "/var/lib/longhorn" # This is the custom path + longhorn_mount_path = local.custom_longhorn_path # This is the custom path } ] ``` -4. Apply the changes. As a result, your external disks will be mounted to `/var/lib/longhorn`. +4. Apply the changes. As a result, your external disks will be mounted to the path defined in `local.custom_longhorn_path`. ### How to configure Longhorn to use the new path? @@ -68,7 +73,7 @@ resource "null_resource" "longhorn_patch_external_disk" { "spec": { "disks": { "external-ssd": { - "path": "/var/lib/longhorn", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label. This example assumes all selected nodes use the same path. + "path": "${local.custom_longhorn_path}", # IMPORTANT: This path must match the 'longhorn_mount_path' for the nodes selected by the 'storage=ssd' label. "allowScheduling": true, "tags": ["ssd"] }