#!/usr/bin/env bash # Bootstraps the cka-sim kind cluster (1 control-plane + 3 workers) on podman and preloads images. # Override with CONTAINER_RUNTIME=docker if you ever want docker instead. set -euo pipefail cd "$(dirname "$0")" RT="${CONTAINER_RUNTIME:-podman}" need() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: '$1' is not installed. $2"; exit 1; } } need "$RT" "https://podman.io/docs/installation" need kind "https://kind.sigs.k8s.io/docs/user/quick-start/#installation" need kubectl "https://kubernetes.io/docs/tasks/tools/" need python3 "" if [ "$RT" = podman ]; then export KIND_EXPERIMENTAL_PROVIDER=podman podman info >/dev/null 2>&1 || { echo "ERROR: podman is not working (try: podman info)."; exit 1; } # kind needs cgroup v2 with controller delegation when podman is rootless. if [ "$(id -u)" != 0 ] && [ "$(podman info --format '{{.Host.CgroupsVersion}}' 2>/dev/null)" != "v2" ]; then echo "ERROR: rootless podman needs cgroup v2 for kind. Either enable cgroup v2 or run setup as root (sudo -E ./setup.sh)." exit 1 fi # inotify limits are the #1 cause of pods stuck in ContainerCreating on multi-node kind for k in fs.inotify.max_user_watches:524288 fs.inotify.max_user_instances:512; do key=${k%%:*}; want=${k##*:}; have=$(sysctl -n "$key" 2>/dev/null || echo 0) [ "$have" -ge "$want" ] || echo "warn: $key=$have (recommend >= $want: sudo sysctl $key=$want)" done else docker info >/dev/null 2>&1 || { echo "ERROR: docker daemon is not running/reachable."; exit 1; } fi if kind get clusters 2>/dev/null | grep -qx cka-sim; then echo "==> kind cluster 'cka-sim' already exists, reusing it." else echo "==> Creating kind cluster 'cka-sim' on $RT (1 control-plane + 3 workers)..." kind create cluster --config kind-config.yaml ${KIND_NODE_IMAGE:+--image "$KIND_NODE_IMAGE"} --wait 180s fi kubectl config use-context kind-cka-sim >/dev/null echo "==> Waiting for nodes..." kubectl wait --for=condition=Ready nodes --all --timeout=180s echo "==> Preloading images into the cluster (non-fatal if a pull fails)..." # `kind load docker-image` shells out to the docker CLI internally no matter which # provider the cluster uses, so it can't see images that only exist in podman's # store. Save to a tar and use `kind load image-archive` instead, which is # runtime-agnostic. for img in nginx:1.25 nginx:1.27 busybox:1.36 redis:7-alpine; do if "$RT" pull -q "docker.io/library/$img" >/dev/null 2>&1; then tarfile="$(mktemp)" if "$RT" save -o "$tarfile" "docker.io/library/$img" >/dev/null 2>&1 \ && kind load image-archive --name cka-sim "$tarfile" >/dev/null 2>&1; then echo " loaded $img" else echo " warn: could not load $img" fi rm -f "$tarfile" else echo " warn: could not pull $img (tasks will pull at runtime)" fi done echo "==> Making sure the exam workdir exists on the control-plane node..." "$RT" exec cka-sim-control-plane mkdir -p /opt/cka # The real exam gives you vim (and less/jq) on every node; the kind node image is minimal, so # install them once here. Skip with CKA_SKIP_NODE_TOOLS=1. if [ -z "${CKA_SKIP_NODE_TOOLS:-}" ]; then echo "==> Installing editors and tools on the nodes (vim, nano, less, jq)..." for n in $(kind get nodes --name cka-sim); do if "$RT" exec "$n" sh -c 'command -v vim >/dev/null'; then echo " $n already has vim" continue fi if "$RT" exec "$n" sh -c 'DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null 2>&1 && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq vim nano less jq >/dev/null 2>&1'; then "$RT" exec "$n" sh -c 'printf "set tabstop=2\nset expandtab\nset shiftwidth=2\n" > /root/.vimrc' echo " $n ok" else echo " warn: could not install tools on $n (no outbound network?). Fall back to:" echo " $RT cp $n:/path/file /tmp/f && \$EDITOR /tmp/f && $RT cp /tmp/f $n:/path/file" fi done fi # The exam expects etcdctl/etcdutl on the control-plane node, like the real one. They cannot be # copied out of the running etcd pod (that image is distroless), so install them properly. echo "==> Installing etcdctl/etcdutl on the control-plane node..." printf ' ' CONTAINER_RUNTIME="$RT" bash ./etcd-tools.sh || echo " warn: no etcd client on the control-plane node; Q02/Q102 will say so when they seed" # remember the runtime so the CLI uses the matching exec command mkdir -p .state && echo "$RT" > .state/runtime chmod +x exam echo echo "Cluster ready. Nodes:" kubectl get nodes -o wide echo echo "Next: ./exam start (seeds all 20 tasks and starts the 120-minute clock)"