local-path-provisioner
local-path-provisioner
is the cluster’s default StorageClass. Every PVC that doesn’t name a
specific class gets a node-local directory under /opt/local-path-provisioner
on whichever worker the consuming Pod schedules to. It is the simplest
working storage backend, and it is the lab’s deliberate Phase 1 choice
— Longhorn for replicated block storage is the Phase 2 deferral,
gated on a Talos extensions upgrade and a separate UserVolume patch.
Why Terraform installs it
Section titled “Why Terraform installs it”Two reasons. First, ordering: ArgoCD addons that ship a PVC need a
default StorageClass to bind against, and an empty Talos cluster
ships none. Without local-path in place before ArgoCD reconciles its
first PVC-using addon (AdGuard Home was the trigger, see
issue #28), the
addon hangs in Pending. Second, the install needs two non-Helm,
non-manifest knobs that are easier to express in Terraform than as a
sibling Helm release: marking the class default with the
storageclass.kubernetes.io/is-default-class annotation, and labelling
the local-path-storage namespace pod-security.kubernetes.io/enforce: privileged so the helper Pods can use hostPath (see
The PSA carve-out below).
Where the config lives
Section titled “Where the config lives”terraform/bootstrap/local-path.tf
is the complete picture:
- The upstream
deploy/local-path-storage.yamlfromrancher/local-path-provisioner v0.0.32, fetched viadata.httpand applied as a multi-documentkubectl_manifest. The official Helm chart is community-maintained and lags releases; the upstream YAML is the canonical install path. - The
is-default-classannotation on the resultingStorageClass. - The PSA
privilegedlabels on thelocal-path-storagenamespace.
Operational entry points
Section titled “Operational entry points”| Resource | Purpose |
|---|---|
StorageClass/local-path | The cluster default. Any PVC without a storageClassName binds here. |
Namespace/local-path-storage | Where the provisioner Deployment and helper Pods run. PSA privileged. |
tofu output default_storage_class | Prints local-path — used as a sanity check during the bootstrap apply. |
Health check the operator runs after a bootstrap or upgrade:
kubectl get storageclass local-pathkubectl -n local-path-storage get podsThe StorageClass should be marked (default) and the provisioner
Deployment should be Ready.
How addons consume storage
Section titled “How addons consume storage”Addon PVCs target the default StorageClass implicitly:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: <addon>-dataspec: accessModes: [ReadWriteOnce] resources: requests: storage: 1Gi # storageClassName intentionally omitted → default → local-pathProvisioning is lazy: when the consuming Pod schedules, the provisioner
spawns a short-lived helper Pod on that node, the helper creates a
directory under /opt/local-path-provisioner/..., and the PVC binds.
Current consumers in the lab are AdGuard Home (config + query log) and
ArgoCD (Redis cache).
The trade-offs of “local disk” are not abstracted away:
- Single-node durability. A PVC is bound to one node’s disk; if
that node is down, the workload waiting on the PVC stays
ContainerCreating. For Phase 1 this is acceptable — the lab has no replicated-state addon today. - No
ReadWriteMany. local-path is RWO only. Anything that needs multi-writer storage waits for Phase 2. - Helper Pods, not driver-managed. Provisioning happens through spawned Pods rather than a CSI sidecar, which is what makes the PSA carve-out below necessary.
The PSA carve-out
Section titled “The PSA carve-out”The cluster-default Pod Security admission level is baseline, which
forbids hostPath volumes. local-path’s helper Pod uses hostPath to
create the backing directory on the node, so without the explicit
namespace label it would be rejected at admission time inside its own
namespace:
helper-pod-create-pvc-… is forbidden:violates PodSecurity "baseline": hostPath volumesSurfaced when AdGuard Home (the first PVC-using addon) hit it. The fix
is to label local-path-storage privileged for the three PSA modes
(enforce, audit, warn); every other namespace stays at
baseline. The blast radius of privileged is bounded to the one
namespace where the helper Pod can actually run — local-path-provisioner’s
binary uses the downward API to spawn helpers in its own namespace
(helperPod.Namespace = p.namespace), so there is no path by which a
workload in another namespace inherits the carve-out.
Phase 2: Longhorn
Section titled “Phase 2: Longhorn”Replicated block storage is the planned upgrade — see the storage-strategy discussion in CONTEXT and the relevant ADR when written. Longhorn needs:
- The
iscsi-toolsTalos system extension installed on every worker (a Talos image rebuild + reboot). - The
UserVolumepatch on each worker to carve out the disk space Longhorn manages. - Migration of existing local-path PVCs to Longhorn-backed equivalents (or accepted data loss for caches like ArgoCD Redis).
Until that lands, local-path stays the cluster default and any addon that needs replicated storage is deferred along with the upgrade.
terraform/bootstrap/local-path.tf
documents the helper-Pod / PSA reasoning inline; it’s the canonical
source for why the namespace is privileged. The recovery procedure
for a failed apply is in
terraform/bootstrap/README.md.