#!/bin/bash # Every boot of a VM exam host (ksim-firstboot.service). The hypervisor hands over two things # in a small shared directory: the host's name and the simulator's public ssh key. On the first boot the node # then becomes a single-node kubeadm cluster of its own, under that name - which is why the cluster # is not baked into the base disk: its node name and certificates would be the same on every host. exec >> /var/log/ksim-firstboot.log 2>&1 set -x F=/run/ksim-host mkdir -p $F mountpoint -q $F || mount -t 9p -o trans=virtio,version=9p2000.L,ro ksim $F name=$(tr -d ' \n' < $F/hostname 2>/dev/null) if [ -n "$name" ] && [ "$(hostname)" != "$name" ]; then hostnamectl set-hostname "$name" grep -q "^127.0.1.1 " /etc/hosts && sed -i "s/^127.0.1.1 .*/127.0.1.1 $name/" /etc/hosts || echo "127.0.1.1 $name" >> /etc/hosts fi if [ -s $F/authorized_keys ]; then for h in /root /home/candidate; do install -d -m700 $h/.ssh && cat $F/authorized_keys > $h/.ssh/authorized_keys && chmod 600 $h/.ssh/authorized_keys done chown -R candidate:candidate /home/candidate/.ssh fi # the task's files: a directory of the simulator, shared into the VM mkdir -p /opt/course mountpoint -q /opt/course || mount -t 9p -o trans=virtio,version=9p2000.L,msize=262144 course /opt/course [ -f /var/lib/ksim-ready ] && exit 0 kubeadm init --node-name "$(hostname)" --pod-network-cidr=10.244.0.0/16 --kubernetes-version "$(kubeadm version -o short)" \ --apiserver-cert-extra-sans=127.0.0.1,localhost,"$(hostname)" || { echo "kubeadm init failed"; exit 1; } # Pod network: one node, so a bridge is all it takes - no CNI daemon to pull or wait for. # (Nothing here enforces NetworkPolicy; tasks that need that run on the container hosts.) mkdir -p /etc/cni/net.d cat > /etc/cni/net.d/10-ksim.conflist <<'EOF' { "cniVersion": "1.0.0", "name": "ksim", "plugins": [ {"type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "hairpinMode": true, "ipam": {"type": "host-local", "ranges": [[{"subnet": "10.244.0.0/24"}]], "routes": [{"dst": "0.0.0.0/0"}]}}, {"type": "portmap", "capabilities": {"portMappings": true}} ] } EOF export KUBECONFIG=/etc/kubernetes/admin.conf for i in $(seq 1 60); do kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 2; done kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true install -d -m700 /root/.kube && install -m600 /etc/kubernetes/admin.conf /root/.kube/config install -d -m700 -o candidate -g candidate /home/candidate/.kube && install -m600 -o candidate -g candidate /etc/kubernetes/admin.conf /home/candidate/.kube/config for i in $(seq 1 90); do [ "$(kubectl get node "$(hostname)" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)" = True ] && break; sleep 2 done # a task's Pods resolve names from their first second on: the host is ready when cluster DNS is kubectl -n kube-system rollout status deploy coredns --timeout=180s || true touch /var/lib/ksim-ready