local-path-storage: Update provisioner to v0.0.37.
Rebase the manifest on upstream v0.0.37: health probes, a namespaced
Role for helper pods, and narrower cluster RBAC. The provisioner now
passes the volume path as $VOL_DIR instead of -p, so setup.sh and
teardown.sh accept both. Allow the hostPID helper pod template, which
v0.0.37 otherwise rejects; the ZFS hook needs it for nsenter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ib8a64ac42caa0a8369c49393aa242f40116b1680
diff --git a/local-path-storage/local-path-storage.yaml b/local-path-storage/local-path-storage.yaml
index 6046baa..cc80544 100644
--- a/local-path-storage/local-path-storage.yaml
+++ b/local-path-storage/local-path-storage.yaml
@@ -1,67 +1,87 @@
----
+# local-path-provisioner v0.0.37, based on upstream deploy/local-path-storage.yaml
+# with these local changes:
+# - PVs live under /var/k8s; setup.sh/teardown.sh create and unmount one
+# ZFS dataset per PV (rpool/var/k8s/<pv>) in the host mount namespace.
+# - The helper pod needs hostPID and privileged for nsenter.
+# - The namespace (PSA enforce=privileged) is not managed here.
+# setup and teardown below must stay in sync with setup.sh and teardown.sh.
+# The provisioner reads helperPod.yaml only at startup; restart it after
+# changing that.
apiVersion: apps/v1
kind: Deployment
metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"apps/v1beta2","kind":"Deployment","metadata":{"annotations":{},"name":"local-path-provisioner","namespace":"local-path-storage"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"local-path-provisioner"}},"template":{"metadata":{"labels":{"app":"local-path-provisioner"}},"spec":{"containers":[{"command":["local-path-provisioner","--debug","start","--config","/etc/config/config.json"],"env":[{"name":"POD_NAMESPACE","valueFrom":{"fieldRef":{"fieldPath":"metadata.namespace"}}}],"image":"rancher/local-path-provisioner:v0.0.2","imagePullPolicy":"Always","name":"local-path-provisioner","volumeMounts":[{"mountPath":"/etc/config/","name":"config-volume"}]}],"serviceAccountName":"local-path-provisioner-service-account","volumes":[{"configMap":{"name":"local-path-config"},"name":"config-volume"}]}}}}
- labels:
- app: local-path-provisioner
name: local-path-provisioner
namespace: local-path-storage
+ labels:
+ app: local-path-provisioner
spec:
- progressDeadlineSeconds: 600
replicas: 1
- revisionHistoryLimit: 10
selector:
matchLabels:
app: local-path-provisioner
- strategy:
- rollingUpdate:
- maxSurge: 25%
- maxUnavailable: 25%
- type: RollingUpdate
template:
metadata:
labels:
app: local-path-provisioner
spec:
+ serviceAccountName: local-path-provisioner-service-account
containers:
- - command:
+ - name: local-path-provisioner
+ image: docker.io/rancher/local-path-provisioner:v0.0.37
+ imagePullPolicy: IfNotPresent
+ command:
- local-path-provisioner
- --debug
- start
- --config
- /etc/config/config.json
+ # The helper pod needs hostPID for nsenter (see setup.sh).
+ - --allow-unsafe-helper-pod-template
+ ports:
+ - name: health
+ containerPort: 8080
+ protocol: TCP
+ startupProbe:
+ httpGet:
+ path: /health
+ port: health
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 12
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: health
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: health
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/config/
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
- apiVersion: v1
fieldPath: metadata.namespace
- image: rancher/local-path-provisioner:v0.0.19
- imagePullPolicy: Always
- name: local-path-provisioner
- resources: {}
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- volumeMounts:
- - mountPath: /etc/config/
- name: config-volume
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- serviceAccount: local-path-provisioner-service-account
- serviceAccountName: local-path-provisioner-service-account
- terminationGracePeriodSeconds: 30
+ - name: CONFIG_MOUNT_PATH
+ value: /etc/config/
+ - name: HEALTH_PORT
+ value: "8080"
volumes:
- - configMap:
- defaultMode: 420
+ - name: config-volume
+ configMap:
name: local-path-config
- name: config-volume
---
apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: local-path-config
+ namespace: local-path-storage
data:
config.json: |-
{
@@ -72,14 +92,20 @@
}
]
}
- helperPod.yaml: "apiVersion: v1\nkind: Pod\nmetadata:\n name: helper-pod\nspec:\n hostPID: true\n containers:\n - name: helper-pod\n image: alpine:3.20\n securityContext:\n privileged: true\n"
- setup: "#!/bin/sh\n# Creates a ZFS dataset per PV. The helper pod runs with hostPID, and all\n# ZFS and filesystem operations are performed in the host's mount namespace\n# (nsenter -t 1 -m). Running zfs directly inside the helper container would\n# mount the dataset only inside the container's own mount namespace; the\n# workload would then write into the parent dataset, and the child dataset\n# would shadow that data after the next host reboot.\nset -eu\n\nwhile getopts \"m:s:p:\" opt\ndo\n case $opt in\n p)\n absolutePath=$OPTARG\n ;;\n s)\n sizeInBytes=$OPTARG\n ;;\n m)\n volMode=$OPTARG\n ;;\n esac\ndone\n\nhost() { nsenter -t 1 -m -- \"$@\"; }\n\nhost zfs create \"rpool${absolutePath}\"\nhost zfs mount \"rpool${absolutePath}\" 2>/dev/null || :\nhost findmnt -n -o SOURCE \"${absolutePath}\" | grep -qx \"rpool${absolutePath}\"\nhost chmod 0777 \"${absolutePath}\"\n"
- teardown: |
+ setup: |
#!/bin/sh
- # Unmounts (but keeps) the PV's ZFS dataset. Runs in the host's mount
- # namespace; see setup.sh.
+ # Creates a ZFS dataset per PV. The helper pod runs with hostPID, and all
+ # ZFS and filesystem operations are performed in the host's mount namespace
+ # (nsenter -t 1 -m). Running zfs directly inside the helper container would
+ # mount the dataset only inside the container's own mount namespace; the
+ # workload would then write into the parent dataset, and the child dataset
+ # would shadow that data after the next host reboot.
+ #
+ # The provisioner passes the volume path as $VOL_DIR (v0.0.2x and later);
+ # older versions passed it as -p.
set -eu
+ absolutePath=${VOL_DIR:-}
while getopts "m:s:p:" opt
do
case $opt in
@@ -94,25 +120,89 @@
;;
esac
done
+ [ -n "$absolutePath" ] || { echo "setup: no volume path given" >&2; exit 1; }
+
+ host() { nsenter -t 1 -m -- "$@"; }
+
+ host zfs create "rpool${absolutePath}"
+ host zfs mount "rpool${absolutePath}" 2>/dev/null || :
+ host findmnt -n -o SOURCE "${absolutePath}" | grep -qx "rpool${absolutePath}"
+ host chmod 0777 "${absolutePath}"
+ teardown: |
+ #!/bin/sh
+ # Unmounts (but keeps) the PV's ZFS dataset. Runs in the host's mount
+ # namespace; see setup.sh.
+ set -eu
+
+ absolutePath=${VOL_DIR:-}
+ while getopts "m:s:p:" opt
+ do
+ case $opt in
+ p)
+ absolutePath=$OPTARG
+ ;;
+ s)
+ sizeInBytes=$OPTARG
+ ;;
+ m)
+ volMode=$OPTARG
+ ;;
+ esac
+ done
+ [ -n "$absolutePath" ] || { echo "teardown: no volume path given" >&2; exit 1; }
nsenter -t 1 -m -- zfs set mountpoint=none "rpool${absolutePath}"
-kind: ConfigMap
-metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"v1","data":{"config.json":"{\n \"nodePathMap\":[\n {\n \"node\":\"DEFAULT_PATH_FOR_NON_LISTED_NODES\",\n \"paths\":[\"/var/k8s\"]\n }\n ]\n}"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"local-path-config","namespace":"local-path-storage"}}
- name: local-path-config
- namespace: local-path-storage
+ helperPod.yaml: |
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: helper-pod
+ spec:
+ priorityClassName: system-node-critical
+ tolerations:
+ - key: node.kubernetes.io/disk-pressure
+ operator: Exists
+ effect: NoSchedule
+ hostPID: true
+ containers:
+ - name: helper-pod
+ image: alpine:3.20
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ privileged: true
---
apiVersion: v1
kind: ServiceAccount
metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"local-path-provisioner-service-account","namespace":"local-path-storage"}}
name: local-path-provisioner-service-account
namespace: local-path-storage
---
+# Since v0.0.24 helper pods run in the provisioner's namespace.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: local-path-provisioner-role
+ namespace: local-path-storage
+rules:
+ - apiGroups: [""]
+ resources: ["pods"]
+ verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: local-path-provisioner-bind
+ namespace: local-path-storage
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: local-path-provisioner-role
+subjects:
+ - kind: ServiceAccount
+ name: local-path-provisioner-service-account
+ namespace: local-path-storage
+---
+# Leftover from PodSecurityPolicy times (psp:default no longer grants anything).
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
@@ -130,51 +220,24 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRole","metadata":{"annotations":{},"name":"local-path-provisioner-role"},"rules":[{"apiGroups":[""],"resources":["nodes","persistentvolumeclaims"],"verbs":["get","list","watch"]},{"apiGroups":[""],"resources":["endpoints","persistentvolumes","pods"],"verbs":["*"]},{"apiGroups":[""],"resources":["events"],"verbs":["create","patch"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses"],"verbs":["get","list","watch"]}]}
name: local-path-provisioner-role
rules:
- - apiGroups:
- - ""
- resources:
- - nodes
- - persistentvolumeclaims
- - configmaps
- verbs:
- - get
- - list
- - watch
- - apiGroups:
- - ""
- resources:
- - endpoints
- - persistentvolumes
- - pods
- verbs:
- - '*'
- - apiGroups:
- - ""
- resources:
- - events
- verbs:
- - create
- - patch
- - apiGroups:
- - storage.k8s.io
- resources:
- - storageclasses
- verbs:
- - get
- - list
- - watch
+ - apiGroups: [""]
+ resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"]
+ verbs: ["get", "list", "watch"]
+ - apiGroups: [""]
+ resources: ["persistentvolumes"]
+ verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
+ - apiGroups: [""]
+ resources: ["events"]
+ verbs: ["create", "patch"]
+ - apiGroups: ["storage.k8s.io"]
+ resources: ["storageclasses"]
+ verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRoleBinding","metadata":{"annotations":{},"name":"local-path-provisioner-bind"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"local-path-provisioner-role"},"subjects":[{"kind":"ServiceAccount","name":"local-path-provisioner-service-account","namespace":"local-path-storage"}]}
name: local-path-provisioner-bind
roleRef:
apiGroup: rbac.authorization.k8s.io
@@ -188,10 +251,7 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
- annotations:
- kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"local-path"},"provisioner":"rancher.io/local-path","reclaimPolicy":"Delete","volumeBindingMode":"WaitForFirstConsumer"}
name: local-path
provisioner: rancher.io/local-path
-reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
+reclaimPolicy: Delete
diff --git a/local-path-storage/setup.sh b/local-path-storage/setup.sh
index aff8c0c..4653e9f 100644
--- a/local-path-storage/setup.sh
+++ b/local-path-storage/setup.sh
@@ -5,8 +5,12 @@
# mount the dataset only inside the container's own mount namespace; the
# workload would then write into the parent dataset, and the child dataset
# would shadow that data after the next host reboot.
+#
+# The provisioner passes the volume path as $VOL_DIR (v0.0.2x and later);
+# older versions passed it as -p.
set -eu
+absolutePath=${VOL_DIR:-}
while getopts "m:s:p:" opt
do
case $opt in
@@ -21,6 +25,7 @@
;;
esac
done
+[ -n "$absolutePath" ] || { echo "setup: no volume path given" >&2; exit 1; }
host() { nsenter -t 1 -m -- "$@"; }
diff --git a/local-path-storage/teardown.sh b/local-path-storage/teardown.sh
index 75c6e9e..6a75f50 100644
--- a/local-path-storage/teardown.sh
+++ b/local-path-storage/teardown.sh
@@ -3,6 +3,7 @@
# namespace; see setup.sh.
set -eu
+absolutePath=${VOL_DIR:-}
while getopts "m:s:p:" opt
do
case $opt in
@@ -17,5 +18,6 @@
;;
esac
done
+[ -n "$absolutePath" ] || { echo "teardown: no volume path given" >&2; exit 1; }
nsenter -t 1 -m -- zfs set mountpoint=none "rpool${absolutePath}"