Files
substrate/hack/setup-csi-hostpath-kind.sh
T
Haven Xia 9df8ca6abe [Upgrade] Version the install: node labels and a per-version atelet DaemonSet (#1357)
A rolling upgrade needs two substrate versions running in one cluster,
split by node: `atelet` and `ateom` speak a node-local protocol with no
cross-version guarantees, so everything on a node has to come from one
build. Today nothing records which build a node runs, and the `atelet`
DaemonSet is a single fixed-name object, so a second version can only
replace the first in place, instead of our porposed node by node rolling
([design](https://docs.google.com/document/d/1JduAyGZFyqdNp4UhKiv0EN-is3iW5tf5BwGTWbt2ouI/edit?usp=sharing&resourcekey=0-MXG12QCkleIxhY7cOB7g6A)).

This PR is the basis for upgrade. Nodes and the `atelet` DaemonSet get
keyed by an `ate.dev/substrate-version` label whose value is the build
version stamped into the binaries.

### One derivation for the version label

`internal/versionlabel` turns the build version into its two forms in
one place:

| form | grammar | used for |
|---|---|---|
| label value | k8s label value | node labels, DaemonSet labels,
nodeSelectors |
| name suffix | DNS-1123 | the DaemonSet name `atelet-<suffix>` |

A version that is not a valid label value is rejected, because the label
has to match what the `ldflags` stamp put into the binaries through
MAKEFILE and `ko`. It's also exposed shell, so the install script can
use it.

### A DaemonSet per version

`atelet-<suffix>` as the name, the version label on metadata, selector,
and pod template, and a pod nodeSelector on the same label. Two versions
run side by side on disjoint old/new node sets.

### The install becomes version-aware

- `install-ate.sh` reads the version from the same `make ldflags` output
it stamps the binaries with, then fills the manifest placeholders.
- It labels the nodes that exist at install time, nodes come after it
carry no version label yet (so add notes in README). Re-running the
install is idempotent, this is the basis for upgrade runbook later.
- The `README` documents the invariant, how to read the installed
version off the DaemonSet, and that a node added later hosts no workers
until it carries the label. On GKE the node pool label is the birth
default for new nodes - added in `tools/setup-gcp/README.md`.

### Follow-ups
- `ate-setup` need same modification
- minimize the affect for daily developer (pin the `VERSION` as
`{USER}-dev` instead of from git
- the manual rolling-upgrade runbook.

Fix #1270
Ref [design
doc](https://docs.google.com/document/d/1JduAyGZFyqdNp4UhKiv0EN-is3iW5tf5BwGTWbt2ouI/edit?usp=sharing&resourcekey=0-MXG12QCkleIxhY7cOB7g6A)
2026-09-01 12:40:49 -07:00

142 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# TODO: Consider integrating this script into install-ate-kind.sh (perhaps as an optional flag).
set -o errexit -o nounset -o pipefail
ROOT="$(git rev-parse --show-toplevel)"
# Define paths
DRIVER_DIR="${ROOT}/hack/third_party/csi-driver-host-path"
DEPLOY_DIR="${DRIVER_DIR}/deploy"
# 1. Clean up existing deployment if present
echo "Cleaning up existing CSI Hostpath resources..."
# Use kustomize to delete if kustomization is valid, ignore errors
kubectl delete -k "${DEPLOY_DIR}" >/dev/null 2>&1 || true
# Also delete our custom service and SC
kubectl delete service csi-hostpath-controller -n default >/dev/null 2>&1 || true
kubectl delete storageclass csi-hostpath-sc >/dev/null 2>&1 || true
# Also clean up the host directories inside Kind node (best effort)
echo "Cleaning up CSI directories on Kind node..."
KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}"
KIND_NODE=$(kubectl get nodes -l '!node-role.kubernetes.io/control-plane' -o name | head -n 1 | cut -d'/' -f2)
if [ -z "${KIND_NODE}" ]; then
KIND_NODE=$(kubectl get nodes -o name | head -n 1 | cut -d'/' -f2)
fi
echo "Using Kind node: ${KIND_NODE}"
if docker ps | grep -q "${KIND_NODE}"; then
# Unmount any stale mounts to prevent "device or resource busy"
docker exec "${KIND_NODE}" sh -c '
for mnt in $(mount | grep /var/lib/ateom-gvisor | awk "{print \$3}"); do
echo "Unmounting stale mount: ${mnt}"
umount -f "${mnt}" || true
done
' || true
# Only delete contents, keep the directory itself to preserve mounts of running pods (like atelet)
docker exec "${KIND_NODE}" sh -c 'rm -rf /var/lib/ateom-gvisor/*' || true
else
echo "Warning: Kind node ${KIND_NODE} not running. Skipping directory cleanup."
fi
# 2 Expose CSI Controller over TCP Service.
# This Service must be created before the driver pod deploys for the servicednssigner
# because servicednssigner to issue a certificate.
echo "Exposing CSI Controller over TCP Service..."
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: csi-hostpath-controller
namespace: default
spec:
selector:
app.kubernetes.io/name: csi-hostpath-socat
ports:
- port: 50051
targetPort: 10000
name: grpc
EOF
# 3. Deploy the CSI Hostpath Driver
echo "Deploying CSI Hostpath Driver..."
# The apply might fail at snapshotclass due to missing CRDs. We catch and ignore this.
kubectl apply -k "${DEPLOY_DIR}" || {
echo "Warning: kubectl apply -k exited with error (likely VolumeSnapshotClass CRD missing). Checking if plugin StatefulSet is created..."
}
# Verify StatefulSet exists
if ! kubectl get statefulset csi-hostpathplugin -n default >/dev/null 2>&1; then
echo "Error: csi-hostpathplugin StatefulSet was not created!"
exit 1
fi
# 4. Patch the CSI Driver to mount Substrate directory
echo "Patching CSI Hostpath StatefulSet..."
kubectl patch statefulset csi-hostpathplugin -n default --patch "
spec:
template:
spec:
nodeSelector:
kubernetes.io/hostname: ${KIND_NODE}
containers:
- name: hostpath
volumeMounts:
- name: ateom-dir
mountPath: /var/lib/ateom-gvisor
mountPropagation: Bidirectional
volumes:
- name: ateom-dir
hostPath:
path: /var/lib/ateom-gvisor
type: DirectoryOrCreate
"
# 6. Create the StorageClass
echo "Creating csi-hostpath-sc StorageClass..."
kubectl apply -f "${DRIVER_DIR}/examples/csi-storageclass.yaml"
# 7. Wait for pods to be ready
echo "Waiting for CSI Hostpath and Proxy pods to be Ready..."
kubectl rollout status statefulset/csi-hostpathplugin -n default --timeout=120s
kubectl rollout status statefulset/csi-hostpath-socat -n default --timeout=120s
# 8. Create the CSIDriverConfig for Substrate
echo "Creating hostpath.csi.k8s.io CSIDriverConfig..."
cat <<EOF | kubectl apply -f -
apiVersion: ate.dev/v1alpha1
kind: CSIDriverConfig
metadata:
name: hostpath.csi.k8s.io
spec:
driverName: hostpath.csi.k8s.io
controllerEndpoint: tcp://csi-hostpath-controller.default.svc.cluster.local:50051
nodeSocketOverride: unix:///var/lib/kubelet/plugins/csi-hostpath/csi.sock
tls:
enabled: true
usePodIdentity: true
serverName: csi-hostpath-controller.default.svc
EOF
# 9. Restart atelet to recreate image cache directories if they were wiped.
# atelet DaemonSet names carry a build-version suffix, so select by label.
echo "Restarting atelet DaemonSet (if present)..."
kubectl rollout restart daemonset -l app=atelet -n ate-system >/dev/null 2>&1 || true
echo "CSI Hostpath setup complete!"