cka-sim/etcd-tools.sh
vumast3r 23a9774a9f fix: install etcdctl/etcdutl on the control-plane node for the etcd tasks
The Q02/Q102 seeds tried to copy etcdctl out of the running etcd pod with
`kubectl exec -- cat`, which cannot work: registry.k8s.io/etcd is distroless
(no cat, no sh, no tar). The copy failed, stderr went to /dev/null and seeding
still printed a green tick, so the snapshot task had no etcdctl on the node.
Q102 was worse: it also ran `exec -- sh -c 'etcdctl ... snapshot save'` inside
that container, so its /opt/cka/backup.db was never created at all.

- etcd-tools.sh installs both binaries on the control-plane node, taking them
  from the etcd image already in the node's containerd (offline, exactly the
  server version), falling back to the upstream release tarball, then apt.
- setup.sh runs it at bootstrap; ensure_etcd_tools in the exam prelude installs
  them through CKA_NODE_ACCESS when a task is seeded, so ssh/vagrant clusters
  get them too, and both etcd setups now fail with a reason instead of hiding it.
- Q102 takes its snapshot with the node's etcdctl; Q02 no longer documents a
  kubectl cp fallback that cannot work on a distroless image.

Co-authored-by: Junie <junie@jetbrains.com>
2026-09-07 13:51:37 -06:00

73 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install etcdctl/etcdutl on the control-plane node (the real exam has them on the node, and
# Q02 (snapshot) / Q102 (restore) need them there). They cannot be copied out of the running
# etcd pod: registry.k8s.io/etcd is distroless, so there is no cat, sh or tar inside it.
# Re-run this after ./setup.sh if the install was skipped or the network was down.
# ./etcd-tools.sh install them on the control-plane node (podman/docker clusters)
# ./etcd-tools.sh --emit print the node-side script; ./exam pipes it through CKA_NODE_ACCESS,
# so ssh/vagrant clusters get the same install when a task is seeded
set -euo pipefail
cd "$(dirname "$0")"
# Runs on the node, as root, POSIX sh only. Three sources, best first:
# 1. the etcd image already in the node's containerd - offline, exactly the server version
# 2. the matching upstream release tarball - needs network, exactly the server version
# 3. the distro's etcd-client package - needs network, etcdctl only, version skew
NODE_SCRIPT=$(cat <<'EOS'
set -u
PATH="/usr/local/bin:$PATH"; export PATH
BIN=/usr/local/bin
have() { command -v etcdctl >/dev/null 2>&1 && command -v etcdutl >/dev/null 2>&1; }
have && { echo "already installed: $(etcdctl version | head -1)"; exit 0; }
img=$(ctr -n k8s.io images ls -q 2>/dev/null | grep -m1 -E '(^|/)etcd:[0-9]' || true)
ver=$(echo "$img" | sed 's/.*etcd://; s/-[0-9]*$//')
if [ -n "$img" ]; then
mnt=$(mktemp -d)
if ctr -n k8s.io images mount --rw "$img" "$mnt" >/dev/null 2>&1; then
cp "$mnt/usr/local/bin/etcdctl" "$mnt/usr/local/bin/etcdutl" "$BIN/" 2>/dev/null || true
ctr -n k8s.io images unmount --rm "$mnt" >/dev/null 2>&1 || true
fi
rmdir "$mnt" 2>/dev/null || true
chmod +x "$BIN/etcdctl" "$BIN/etcdutl" 2>/dev/null || true
have && { echo "installed $ver from $img"; exit 0; }
fi
if [ -n "$ver" ] && command -v curl >/dev/null 2>&1; then
arch=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
d="etcd-v$ver-linux-$arch"
curl -sfL --max-time 180 "https://github.com/etcd-io/etcd/releases/download/v$ver/$d.tar.gz" \
| tar -xzf - -C "$BIN" --strip-components=1 "$d/etcdctl" "$d/etcdutl" 2>/dev/null || true
chmod +x "$BIN/etcdctl" "$BIN/etcdutl" 2>/dev/null || true
have && { echo "installed $ver from the upstream release"; exit 0; }
fi
if command -v apt-get >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null 2>&1 || true
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq etcd-client >/dev/null 2>&1 || true
command -v etcdctl >/dev/null 2>&1 && {
echo "installed $(etcdctl version | head -1 | awk '{print $3}') from apt: no etcdutl (use"
echo "'etcdctl snapshot restore') and it may not match the ${ver:-running} server"
exit 0
}
fi
echo "ERROR: could not install etcdctl/etcdutl. Tried the etcd image in containerd"
echo "(${img:-none found}), the upstream release and apt-get (needs outbound network)."
exit 1
EOS
)
if [ "${1:-}" = --emit ]; then
printf '%s\n' "$NODE_SCRIPT"
exit 0
fi
RT="${CONTAINER_RUNTIME:-$(cat .state/runtime 2>/dev/null || echo podman)}"
[ "$RT" = podman ] && export KIND_EXPERIMENTAL_PROVIDER=podman
CP="${CKA_CP:-cka-sim-control-plane}"
printf '%s: ' "$CP"
printf '%s\n' "$NODE_SCRIPT" | "$RT" exec -i "$CP" sh -s