--- name: educates-upgrade-vcluster description: Upgrade the vcluster software version (and the Kubernetes versions the virtual cluster provisions) used by the vcluster workshop application in session-manager. Invoke when asked to "upgrade vcluster", "bump vcluster", "update vcluster to vX.Y.Z", or "align vcluster kubernetes versions". argument-hint: "[] (e.g. 0.35.2)" allowed-tools: "Read, Edit, Glob, Bash, WebFetch" --- # Educates vcluster Upgrade Protocol Upgrade the `vcluster` workshop application to the vcluster version in `$ARGUMENTS` (e.g. `0.35.2`). With no argument, find the latest stable release at `https://github.com/loft-sh/vcluster/releases` and confirm the target with the user before proceeding. This is a **runtime-component** change (`session-manager/`). Confirm the scope with the user before starting if it was not explicitly requested. ## Why this is not a one-line bump Educates does **not** `helm install` vcluster at runtime. session-manager (`session-manager/handlers/application_vcluster.py`) deploys each virtual cluster by applying **hand-written host-cluster objects** and embeds the vcluster config as a Secret (`vc-config-my-vcluster`, key `config.yaml`) mounted into the control-plane pod. The **StatefulSet, Services, and RBAC in that file are hand-maintained transcriptions of what the upstream vcluster Helm chart renders**. Nothing regenerates them, so a version bump means re-rendering the chart and reconciling those objects by hand, preserving Educates' own customizations. Since vcluster 0.20 the `k8s` distro runs in-process inside the single `syncer` container (no separate apiserver/etcd Deployment); an init container stages the Kubernetes binaries from the `loftsh-kubernetes` image. vcluster's Helm **chart version equals its app/binary version** (chart `0.35.2` ships vcluster `0.35.2`), unlike the cluster-service charts. There is no separate appVersion to resolve. ## The version-state surface (all must move together) 1. **`installer/charts/educates-training-platform/charts/session-manager/templates/_helpers.tpl`** — the `imageVersions` inventory: `loftsh-vcluster` (`ghcr.io/loft-sh/vcluster-oss:` — Educates uses the **OSS** variant), the `loftsh-kubernetes-v1.NN` image set, and `vcluster-internal-contour` / `vcluster-internal-envoy` (the Contour/Envoy used only when a workshop enables vcluster ingress). The air-gap image list flows from here automatically via `hack/generate-image-list.sh` — no separate edit. 2. **`session-manager/handlers/operator_config.py`** — the `LOFTSH_KUBERNETES_V1_NN_IMAGE = image_reference(...)` constants and `LOFTSH_VCLUSTER_IMAGE` (the latter is version-agnostic; it resolves through the inventory). 3. **`session-manager/handlers/application_vcluster.py`** — the `operator_config` imports, the `K8S_VERSIONS` map, and `K8S_DEFAULT_VERSION`, plus the embedded StatefulSet / Service / RBAC objects (see reconciliation below). 4. **`session-manager/packages/vcluster/vcluster-all-config.yaml`** — the upstream chart `values.yaml`, copied verbatim (with comments) for the target tag. session-manager loads it, applies a handful of Educates overrides, and ships it as the config Secret. Update the version pin in its 3-line header. ## Release-page artifacts From `https://github.com/loft-sh/vcluster/releases/tag/v`: - **`images-optional.txt`** — the authoritative list of the **OSS** vcluster image and every **optional** Kubernetes version the release supports (`ghcr.io/loft-sh/kubernetes:v1.NN.p`), plus etcd/coredns variants. This is where the supported k8s set and the exact patch versions come from. - **`images.txt`** — the **default** images: the `vcluster-pro` image, the **default** k8s version (the newest, not in `images-optional.txt`), and the default coredns. - **`vcluster-images-v1.NN.txt`** (per-k8s-version) — the full image set for **full air-gap** support of that k8s version (etcd, coredns, kine, etc.). Out of scope for a version bump, but note it for the air-gap follow-up. Together, `images.txt` (default k8s) + `images-optional.txt` (optional k8s) define the full supported Kubernetes range for the release. ```bash curl -sSfL https://github.com/loft-sh/vcluster/releases/download/v/images-optional.txt curl -sSfL https://github.com/loft-sh/vcluster/releases/download/v/images.txt ``` ## Keeping vcluster and platform Kubernetes versions aligned **The set of Kubernetes versions the vcluster application provisions (`K8S_VERSIONS`) should match the platform's supported Kubernetes versions** (the kubectl / kind set managed by the **`educates-upgrade-kubernetes`** skill), as long as vcluster's `images-optional.txt` (plus the default in `images.txt`) lists them. `K8S_DEFAULT_VERSION` should match `DefaultKubernetesVersion` in `client-programs/pkg/constants/kubernetes.go`. When you run this skill: 1. Read the platform's current set from `pkg/constants/kubernetes.go` (`KubernetesVersionToKindImage`, `DefaultKubernetesVersion`). 2. Intersect it with what the target vcluster release supports (`images.txt` + `images-optional.txt`). If a platform version is not supported by this vcluster release, **stop and ask the user** how to reconcile (hold vcluster back, or drop that k8s version platform-wide). 3. If the two sets already match and are supported, align `K8S_VERSIONS` to them. If they diverge, **ask the user for permission to run `educates-upgrade-kubernetes`** to bring the platform set in line (or to accept a deliberate divergence), rather than silently picking one. The `educates-upgrade-kubernetes` skill carries the reciprocal reminder. ## Step-by-step 1. **Resolve versions.** Read `images-optional.txt` + `images.txt`. Confirm the `vcluster-oss:` tag and the exact `loftsh-kubernetes:v1.NN.p` patch tags for the k8s set you will support. Reconcile with the platform set (above). 2. **Update the four version-state files** (inventory, constants, handler map + default, values file). Re-copy `vcluster-all-config.yaml` from `https://github.com/loft-sh/vcluster/blob/v/chart/values.yaml` and update its header pin. Fold in any new config sub-trees the new values file introduces. 3. **Render the chart with Educates' effective config** and reconcile the embedded objects: ```bash helm repo add loft https://charts.loft.sh && helm repo update loft # overrides.yaml = the sync toggles application_vcluster.py sets at runtime, # because they change the generated RBAC: cat > overrides.yaml <<'EOF' sync: toHost: serviceAccounts: { enabled: true } ingresses: { enabled: true } fromHost: storageClasses: { enabled: true } ingressClasses: { enabled: true } policies: resourceQuota: { enabled: false } limitRange: { enabled: false } EOF helm template my-vcluster loft/vcluster --version -n my-vcluster-vc \ -f session-manager/packages/vcluster/vcluster-all-config.yaml \ -f overrides.yaml ``` Diff the rendered `StatefulSet`, `Service`s, `ClusterRole`, and `Role` against the embedded dicts. Apply the deltas, but **preserve Educates' customizations** (do not blindly copy the chart): - the `k8s_image` init container and `LOFTSH_VCLUSTER_IMAGE` syncer image; - parametrized resources (`syncer_memory` / `syncer_storage`); - `storageClassName: None` on the data volumeClaimTemplate; - the pod `securityContext` storage-group (`CLUSTER_STORAGE_GROUP`); - the **non-root** syncer `securityContext` (`runAsUser 12345`). vcluster's chart may render this container as **root** (`runAsUser 0`); keep non-root and validate at runtime. If the control plane misbehaves after the bump, revisit whether non-root is still supported upstream. 4. **Reconcile RBAC** (the load-bearing, error-prone step). The embedded `ClusterRole`/`Role`s are **Educates-curated**, not a verbatim chart copy (they add `ingressclasses`/`storageclasses`/`ingresses`/`serviceaccounts` grants for Educates' sync toggles). Add whatever the new render newly requires (past bumps added `pods/resize`, `pods/ephemeralcontainers`, `events.k8s.io` events, `endpointslices` create/delete). The `-vc` namespace Role is the syncer's sync-target Role (it holds `pods/status`); the session-namespace Role is intentionally narrower. **Mirror every added rule into `educates-session-manager:vcluster` in `installer/charts/educates-training-platform/charts/session-manager/templates/clusterroles.yaml`** — Kubernetes privilege-escalation prevention means session-manager can only create the per-session Roles if it already holds every permission they grant, so that ClusterRole must stay a superset. Under-granting shows up at runtime as a `403 "attempting to grant RBAC permissions not currently held"` when a vcluster session is created. 5. **Release note + docs.** Add a `Features Changed` entry to the current `project-docs/release-notes/version-.md` (vcluster version + the k8s set change). Review `project-docs` for anything that names the vcluster or k8s versions. No emdashes. ## Verify ```bash python3 -c "import ast; ast.parse(open('session-manager/handlers/application_vcluster.py').read())" # Re-render and confirm the embedded StatefulSet/Services/RBAC match the chart # output (modulo the preserved Educates customizations listed above). ``` The authoritative check is a **runtime smoke test**: deploy a workshop whose `session.applications.vcluster.enabled: true`, confirm the control-plane pod reaches Ready, the kubeconfig Secret is copied into the session, and `kubectl` against the virtual cluster works. Exercise a workshop that syncs services / ingresses so the reconciled RBAC is actually hit. ## Post-upgrade checklist - [ ] `_helpers.tpl` inventory: `loftsh-vcluster` (oss) + `loftsh-kubernetes-v1.NN` set updated (exact patch tags from `images-optional.txt` / `images.txt`) - [ ] `operator_config.py` constants match the inventory names - [ ] `application_vcluster.py`: imports, `K8S_VERSIONS`, `K8S_DEFAULT_VERSION` updated - [ ] `K8S_VERSIONS` aligned with the platform set (`pkg/constants/kubernetes.go`), or divergence agreed with the user - [ ] `vcluster-all-config.yaml` re-copied from the tag + header pin updated - [ ] Embedded StatefulSet / Services reconciled against the render, Educates customizations preserved - [ ] Embedded RBAC reconciled AND mirrored into `educates-session-manager:vcluster` (clusterroles.yaml) - [ ] Non-root syncer securityContext kept + flagged for runtime validation - [ ] Release note added; `project-docs` reviewed - [ ] `application_vcluster.py` parses; chart re-render matches; runtime vcluster workshop smoke-tested - [ ] Air-gap follow-up noted (per-k8s-version image txt files) if full air-gap is in scope