111 lines
5.3 KiB
Bash
111 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
HETZNER_CSI_VERSION="${HETZNER_CSI_VERSION:-2.21.2}"
|
|
HETZNER_CSI_STORAGE_CLASS="${HETZNER_CSI_STORAGE_CLASS:-hcloud-volumes}"
|
|
HETZNER_CSI_RECLAIM_POLICY="${HETZNER_CSI_RECLAIM_POLICY:-Retain}"
|
|
HETZNER_CSI_DEFAULT_FS="${HETZNER_CSI_DEFAULT_FS:-ext4}"
|
|
HETZNER_CSI_TOKEN_SECRET="${HETZNER_CSI_TOKEN_SECRET:-hcloud}"
|
|
HETZNER_CSI_ENABLE_SNAPSHOT_CLASS="${HETZNER_CSI_ENABLE_SNAPSHOT_CLASS:-false}"
|
|
# Host kubelet data dir the csi-node DaemonSet binds. Default is the kubeadm path;
|
|
# k0s uses /var/lib/data/k0s/kubelet. The upstream manifest hardcodes /var/lib/kubelet,
|
|
# so we rewrite it below when the target distro differs — otherwise NodeStage mounts
|
|
# into a path kubelet never reads and every PVC silently falls back to local disk.
|
|
HETZNER_CSI_KUBELET_DIR="${HETZNER_CSI_KUBELET_DIR:-/var/lib/kubelet}"
|
|
|
|
# external-snapshotter version compatible with Hetzner CSI 2.x
|
|
SNAPSHOTTER_VERSION="v8.2.0"
|
|
|
|
MANIFEST_URL="https://raw.githubusercontent.com/hetznercloud/csi-driver/v${HETZNER_CSI_VERSION}/deploy/kubernetes/hcloud-csi.yml"
|
|
|
|
echo "=== hetzner-csi: checking prerequisites ==="
|
|
|
|
# Resolve kubectl: k0s kubectl, standard kubectl
|
|
if command -v k0s &>/dev/null && k0s kubectl version --client=true &>/dev/null; then
|
|
kubectl() { k0s kubectl "$@"; }
|
|
elif ! command -v kubectl &>/dev/null; then
|
|
echo "ERROR: neither kubectl nor k0s found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Require the HCLOUD_TOKEN secret to exist before deploying — it must be
|
|
# provisioned out-of-band (SOPS-managed) so we never store the token in plain text.
|
|
if ! kubectl -n kube-system get secret "${HETZNER_CSI_TOKEN_SECRET}" &>/dev/null; then
|
|
echo "ERROR: Secret '${HETZNER_CSI_TOKEN_SECRET}' not found in kube-system." >&2
|
|
echo " Create it first: kubectl -n kube-system create secret generic ${HETZNER_CSI_TOKEN_SECRET} --from-literal=token=<HCLOUD_TOKEN>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== hetzner-csi: deploying v${HETZNER_CSI_VERSION} ==="
|
|
|
|
# Apply official manifest EXCLUDING StorageClass (we manage it separately with custom reclaimPolicy).
|
|
# Rewrite the hardcoded /var/lib/kubelet csi-node paths to the target distro's kubelet dir
|
|
# (no-op when default). /var/lib/kubelet appears only in the node DaemonSet (hostPaths +
|
|
# --kubelet-registration-path), so a global substitution is safe. '#' delimiter because the
|
|
# replacement contains slashes.
|
|
if [ "${HETZNER_CSI_KUBELET_DIR}" != "/var/lib/kubelet" ]; then
|
|
echo "=== hetzner-csi: rewriting csi-node kubelet dir → ${HETZNER_CSI_KUBELET_DIR} ==="
|
|
fi
|
|
curl -sL "${MANIFEST_URL}" \
|
|
| awk 'BEGIN{skip=0} /^kind: StorageClass/{skip=1} /^---/{if(skip){skip=0;next}} {if(!skip)print}' \
|
|
| sed "s#/var/lib/kubelet#${HETZNER_CSI_KUBELET_DIR}#g" \
|
|
| kubectl apply -f -
|
|
|
|
# Create or recreate StorageClass with desired name and reclaimPolicy
|
|
CURRENT_POLICY="$(kubectl get storageclass "${HETZNER_CSI_STORAGE_CLASS}" -o jsonpath='{.reclaimPolicy}' 2>/dev/null || echo "")"
|
|
if [ -z "${CURRENT_POLICY}" ] || [ "${CURRENT_POLICY}" != "${HETZNER_CSI_RECLAIM_POLICY}" ]; then
|
|
[ -n "${CURRENT_POLICY}" ] && kubectl delete storageclass "${HETZNER_CSI_STORAGE_CLASS}" 2>/dev/null || true
|
|
echo "=== hetzner-csi: creating StorageClass ${HETZNER_CSI_STORAGE_CLASS} (reclaimPolicy=${HETZNER_CSI_RECLAIM_POLICY}) ==="
|
|
kubectl apply -f - <<SCEOF
|
|
apiVersion: storage.k8s.io/v1
|
|
kind: StorageClass
|
|
metadata:
|
|
name: ${HETZNER_CSI_STORAGE_CLASS}
|
|
provisioner: csi.hetzner.cloud
|
|
reclaimPolicy: ${HETZNER_CSI_RECLAIM_POLICY}
|
|
volumeBindingMode: WaitForFirstConsumer
|
|
allowVolumeExpansion: true
|
|
SCEOF
|
|
fi
|
|
|
|
# Set default filesystem annotation on the StorageClass
|
|
kubectl annotate storageclass "${HETZNER_CSI_STORAGE_CLASS}" \
|
|
"csi.storage.k8s.io/fstype=${HETZNER_CSI_DEFAULT_FS}" \
|
|
--overwrite
|
|
|
|
echo "=== hetzner-csi: waiting for controller pod ==="
|
|
kubectl rollout status deployment/hcloud-csi-controller -n kube-system --timeout=120s
|
|
|
|
echo "=== hetzner-csi: ready (StorageClass=${HETZNER_CSI_STORAGE_CLASS}, policy=${HETZNER_CSI_RECLAIM_POLICY}, fs=${HETZNER_CSI_DEFAULT_FS}) ==="
|
|
|
|
# Optional: deploy external-snapshotter + VolumeSnapshotClass for K8s-native PVC snapshots
|
|
if [ "${HETZNER_CSI_ENABLE_SNAPSHOT_CLASS}" = "true" ]; then
|
|
echo "=== hetzner-csi: deploying external-snapshotter ${SNAPSHOTTER_VERSION} ==="
|
|
|
|
SNAPSHOTTER_BASE="https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/${SNAPSHOTTER_VERSION}"
|
|
|
|
# CRDs — must apply before the controller
|
|
kubectl apply -f "${SNAPSHOTTER_BASE}/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml"
|
|
kubectl apply -f "${SNAPSHOTTER_BASE}/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml"
|
|
kubectl apply -f "${SNAPSHOTTER_BASE}/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml"
|
|
|
|
# Snapshot controller
|
|
kubectl apply -f "${SNAPSHOTTER_BASE}/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml"
|
|
kubectl apply -f "${SNAPSHOTTER_BASE}/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml"
|
|
|
|
kubectl rollout status deployment/snapshot-controller -n kube-system --timeout=120s
|
|
|
|
# VolumeSnapshotClass using Hetzner CSI driver
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: snapshot.storage.k8s.io/v1
|
|
kind: VolumeSnapshotClass
|
|
metadata:
|
|
name: hcloud-snapshots
|
|
annotations:
|
|
snapshot.storage.kubernetes.io/is-default-class: "false"
|
|
driver: csi.hetzner.cloud
|
|
deletionPolicy: Delete
|
|
EOF
|
|
|
|
echo "=== hetzner-csi: VolumeSnapshotClass 'hcloud-snapshots' ready ==="
|
|
fi
|