This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A KubeVirt / Kubermatic Virtualization training environment. Trainees connect to a code-server "jumphost" container and run labs that install a Kubernetes cluster on two GCE VMs (controlplane-node, worker-node) via the kubev binary, then create and manage KubeVirt VMs on top of it.
The jumphost is a code-server (browser VS Code) container defined by container-image/dockerfile. There is no Makefile — build and run it with docker directly:
# build
docker build -t kubev:0.0.0 ./container-image/
# run detached, mount repo at /training, expose code-server on http://localhost:8080
docker run -d --name kubev -p 8080:8080 -v "$PWD":/training kubev:0.0.0The container runs code-server with --auth none --bind-addr 0.0.0.0:8080; trainees access it in a browser. There is no authentication on the IDE itself — only ever expose it on localhost.
Inside the container, /root/.trainingrc (sourced by .zshrc) sets:
KUBECONFIG=/training/.secrets/kubev-cluster-kubeconfigalias code=code-server- shell completions for kubectl, helm, helmfile, virtctl
PATHincludes/root/.krew/bin
Tooling baked into the image via pinned ARGs in container-image/dockerfile: kubectl (K8S_VERSION=1.34.5), krew (KREW_VERSION=0.5.0), helm (HELM_VERSION=4.1.3), helmfile (HELMFILE_VERSION=1.4.3), virtctl (VIRTCTL_VERSION=1.5.3), plus kubectx and standard net utilities (curl, wget, nmap, traceroute, arping, netcat-openbsd, apache2-utils). Do not bump these pins casually — labs depend on the exact toolchain.
Labs are numbered directories at the repo root, each with a single README.md. The numbering has gaps — there is no 03_*, 05_*, or 10_*.
01_hello-virtualization/— provision a plain libvirt/KVM VM on the worker node directly (no Kubernetes) usingvirt-install+ cloud-init.02_install-binaries/— download thekubermatic-virtualizationbinary from Quay viakubermatic-ee-downloader(usingKUBEV_USERNAME/KUBEV_PASSWORDworkshop credentials), then install it to/usr/local/bin/kubev.04_create-kubermatic-virtualization-cluster/—kubev apply -f /training/cluster.yaml -yprovisions the K8s cluster via kubeone and installs kubev components. The producedkubev-cluster-kubeconfigis then moved into.secrets/so theKUBECONFIGenv var resolves.06_import-image/— applyubuntu-image-datavolume.yamlso CDI downloads the Ubuntu cloud image into a Longhorn PVC.07_create-vm/— spliceuser-data.yamlintomy-vm.yaml, thenkubectl applythe VM.08_connect-to-vm/— connect viavirtctl ssh,virtctl console, NodePort service (my-vm-service.yaml).09_persistence/— add a blankDataVolumedisk tomy-vm.yaml, restart the VM, format and mount inside the guest, verify on the worker via Longhorn replicas.11_dashboard/— extendcluster.yamlwith adashboard:block (basic auth), re-apply withkubev, expose thekubev-dashboardservice via NodePort, read credentials from thekubev-basic-authsecret.99_teardown/—teardown.shrunskubeadm resetandapt removeof k8s packages; the README ssh's it to both nodes and cleans local kubeconfig/binary files.
All referenced as /training/<file> from the labs because the repo is mounted at /training inside the container.
cluster.yaml—KubeVClusterspec. Internal IPs of control-plane / worker VMs are placeholders (<FILL-IN-INTERNAL-IP-OF-*>) that trainees fill in from.secrets/README.md. Network CIDR172.25.0.0/24, MetalLB pool10.156.0.193-10.156.0.254(GCE-routable range), storage: Longhorn.my-vm.yaml—kubevirt.io/v1 VirtualMachine, 2 vCPU / 2Gi, sources rootdisk from PVCubuntu-image. Thecloudinitvolume'suserDatais a<FILL-IN-CLOUD-CONFIG>placeholder spliced in during lab 07.ubuntu-image-datavolume.yaml— CDIDataVolumepulling Ubuntu 24.04 noble cloudimg into a 10Gi Longhorn PVC.user-data.yaml— cloud-init user data fragment pasted intomy-vm.yaml. Contains the (intentional) training root passwordchang3Me#.my-vm-service.yaml— Service exposing the VM (NodePort 30022 + LoadBalancer).metallb.yaml— fallbackIPAddressPoolif kubev's installer doesn't create one (workaround for kubermatic-virtualization issue #149).training-application.yaml— sample app exposed via LoadBalancer (MetalLB).network-config— cloud-init network config snippet.
kubev apply writes the produced admin kubeconfig to /training/kubev-cluster-kubeconfig (repo root inside the container, which is the host repo dir). Lab 04 immediately moves it to /training/.secrets/kubev-cluster-kubeconfig where $KUBECONFIG points. Any leftover kubev-cluster-kubeconfig at the repo root is a full cluster-admin credential — .gitignore covers *kubeconfig so it won't be committed, but it must not be shared (zip, tarball, demo screen-share). Lab 99 cleans both locations.
Per repo README.md, trainees receive a .secrets/ bundle out-of-band containing GCP SSH keys (gcp-kubev, gcp-kubev.pub, gcp-kubev-config) and a README.md with the VM IPs. They drop it into /training/.secrets/ and install the keys into /root/.ssh/. After that, ssh controlplane-node and ssh worker-node resolve via the provided SSH config.
- MetalLB external IPs may not be reachable from outside the GCE network — labs note "TODO: GCP networking / firewall issue" for the LoadBalancer paths in
08_connect-to-vm. kubectl port-forward→ ssh into the VM no longer works ("TODO does not work anymore" in lab 08).- MetalLB
IPAddressPoolsometimes isn't created by the installer (kubermatic-virtualization#149);metallb.yamlis the manual fallback. - multus pods may CrashLoop on low memory; the optional helm upgrade in lab 04 redeploys the bundled
charts/multus-cni/chart.
bin/, charts/, *.tar.gz, **-linux-amd64**, *kubeconfig, kubeone_*, tf_infra, .secrets/, .DS_Store, .claude/, and notably CLAUDE.md itself are gitignored — this file is local-only.