From d3f3714edda40d137eeb289e15c76a2580ed3fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Thu, 23 Jul 2026 16:50:36 +0200 Subject: [PATCH] EXPERIMENT: devkit - switch Alpine base to Ubuntu (noble) Build the devkit debug extension from the same Ubuntu release the guest image uses (assets.image ... version, e.g. noble) instead of Alpine. The rootfs is assembled via 'docker build' + 'docker export' so apt resolves dependencies natively, and the guest helper scripts wrap apt-get (devkit-apt) instead of apk. Not for merge; experiment on branch experiment/devkit-ubuntu. --- .../kubernetes/k8s-devkit-debug-console.bats | 15 +-- .../devkit/{devkit-apk.sh => devkit-apt.sh} | 7 +- .../rootfs-builder/devkit/devkit-init.sh | 21 ++-- .../helm-chart/kata-deploy/values.yaml | 2 +- .../local-build/kata-deploy-binaries.sh | 109 +++++++++--------- versions.yaml | 6 - 6 files changed, 78 insertions(+), 82 deletions(-) rename tools/osbuilder/rootfs-builder/devkit/{devkit-apk.sh => devkit-apt.sh} (52%) diff --git a/tests/integration/kubernetes/k8s-devkit-debug-console.bats b/tests/integration/kubernetes/k8s-devkit-debug-console.bats index 6c5fa7f4e4..cfbcfbb0d1 100644 --- a/tests/integration/kubernetes/k8s-devkit-debug-console.bats +++ b/tests/integration/kubernetes/k8s-devkit-debug-console.bats @@ -8,7 +8,7 @@ # (kata-ctl exec ) on the NVIDIA CPU runtime-rs class: # # * With the kata--devkit RuntimeClass, the console drops into the rich -# Alpine-based devkit shell overlaid on the read-only guest rootfs. +# Ubuntu-based devkit shell overlaid on the read-only guest rootfs. # * Without devkit (the base RuntimeClass), the shell-less NVIDIA base cannot # spawn a console shell at all. @@ -34,8 +34,9 @@ devkit_runtimeclass() { # when a real shell runs it. The literal command text, even if echoed back by # the PTY, never contains the expanded value - so "SHELL_OK=42" in the output is # unambiguous proof that a debug console shell actually executed. GUEST_ID and -# apk then tell devkit (Alpine overlay) apart from the base rootfs. -DEVKIT_PROBE='echo "SHELL_OK=$((6*7))"; . /etc/os-release 2>/dev/null; echo "GUEST_ID=${ID}"; command -v apk >/dev/null 2>&1 && apk --version || true' +# the devkit-only devkit-apt wrapper then tell the devkit (Ubuntu) overlay apart +# from the base rootfs. +DEVKIT_PROBE='echo "SHELL_OK=$((6*7))"; . /etc/os-release 2>/dev/null; echo "GUEST_ID=${ID}"; command -v devkit-apt >/dev/null 2>&1 && echo "DEVKIT_TOOL=yes" || true' check_and_skip() { if ! devkit_supported; then @@ -119,10 +120,10 @@ setup() { echo "${output}" | grep -q 'SHELL_OK=42' \ || die "devkit debug console did not provide a working shell" - echo "${output}" | grep -q 'GUEST_ID=alpine' \ - || die "devkit debug console did not report an Alpine guest" - echo "${output}" | grep -q 'apk-tools' \ - || die "devkit debug console shell lacks apk; not the devkit overlay" + echo "${output}" | grep -q 'GUEST_ID=ubuntu' \ + || die "devkit debug console did not report an Ubuntu guest" + echo "${output}" | grep -q 'DEVKIT_TOOL=yes' \ + || die "devkit debug console shell lacks devkit-apt; not the devkit overlay" } @test "Without devkit, the NVIDIA base debug console has no usable shell" { diff --git a/tools/osbuilder/rootfs-builder/devkit/devkit-apk.sh b/tools/osbuilder/rootfs-builder/devkit/devkit-apt.sh similarity index 52% rename from tools/osbuilder/rootfs-builder/devkit/devkit-apk.sh rename to tools/osbuilder/rootfs-builder/devkit/devkit-apt.sh index 0a796512a7..81178c43e1 100644 --- a/tools/osbuilder/rootfs-builder/devkit/devkit-apk.sh +++ b/tools/osbuilder/rootfs-builder/devkit/devkit-apt.sh @@ -5,10 +5,11 @@ # # SPDX-License-Identifier: Apache-2.0 # -# Thin apk wrapper: runs Alpine's apk inside the devkit overlay/chroot, so -# `devkit-apk add htop` installs into the writable overlay at runtime. +# Thin apt wrapper: runs Ubuntu's apt-get inside the devkit overlay/chroot, so +# `devkit-apt update && devkit-apt install -y htop` installs into the writable +# overlay at runtime (an offline base ships no package lists, hence the update). DEVKIT=/run/kata-extensions/devkit # shellcheck source=tools/osbuilder/rootfs-builder/devkit/devkit-init.sh . "${DEVKIT}/usr/bin/devkit-init" -devkit_chroot_exec /sbin/apk "$@" +devkit_chroot_exec /usr/bin/apt-get "$@" diff --git a/tools/osbuilder/rootfs-builder/devkit/devkit-init.sh b/tools/osbuilder/rootfs-builder/devkit/devkit-init.sh index 2a945fb33f..0f7395ca6b 100644 --- a/tools/osbuilder/rootfs-builder/devkit/devkit-init.sh +++ b/tools/osbuilder/rootfs-builder/devkit/devkit-init.sh @@ -5,17 +5,16 @@ # # SPDX-License-Identifier: Apache-2.0 # -# Shared devkit guest runtime library, sourced by devkit-enter and devkit-apk. +# Shared devkit guest runtime library, sourced by devkit-enter and devkit-apt. # -# The devkit extension (read-only at ${DEVKIT}) is a full minimal Alpine rootfs. -# We overlay a writable, exec-enabled tmpfs on it and chroot in, so apk and the +# The devkit extension (read-only at ${DEVKIT}) is a full minimal Ubuntu rootfs. +# We overlay a writable, exec-enabled tmpfs on it and chroot in, so apt and the # prebaked tools run natively against a normal root filesystem. # -# BB is Alpine's statically-linked busybox (busybox-static, /bin/busybox.static), -# the ONLY binary we can exec on the shell-less guest base before the musl loader -# exists. We deliberately do NOT reuse /bin/busybox: that is Alpine's dynamic -# busybox providing the chroot's coreutils applets, and clobbering it breaks -# ls/cat/ps and apk's busybox trigger. +# BB is a statically-linked busybox (busybox-static, /bin/busybox.static), the +# ONLY binary we can exec on the shell-less guest base before the glibc dynamic +# loader is reachable there. It is installed at a dedicated path so it never +# clobbers the rootfs's own /bin/busybox or GNU coreutils. DEVKIT_INIT_VERSION=1 @@ -33,7 +32,7 @@ devkit_is_mounted() { # Mount an exec-enabled tmpfs at ${WRITABLE}: /run is typically noexec, so the # overlay upper/work (and the fallback rootfs copy) must live on our own tmpfs -# for the prebaked and apk-installed binaries to exec. +# for the prebaked and apt-installed binaries to exec. devkit_mount_writable() { "${BB}" mkdir -p "${WRITABLE}" devkit_is_mounted "${WRITABLE}" && return 0 @@ -60,7 +59,7 @@ devkit_mount_root() { "${BB}" touch "${WRITABLE}/.copied" } -# Bind the kernel virtual filesystems so apk and the debug tools behave; /dev is +# Bind the kernel virtual filesystems so apt and the debug tools behave; /dev is # rbind'd to bring in pts/shm for interactive shells. devkit_bind_mounts() { "${BB}" mkdir -p "${MERGED}/proc" "${MERGED}/sys" "${MERGED}/dev" @@ -78,7 +77,7 @@ devkit_bind_mounts() { fi } -# Give apk/curl working DNS by importing the guest resolver config. +# Give apt/curl working DNS by importing the guest resolver config. devkit_seed_resolv_conf() { "${BB}" test -e /etc/resolv.conf || return 0 "${BB}" mkdir -p "${MERGED}/etc" diff --git a/tools/packaging/kata-deploy/helm-chart/kata-deploy/values.yaml b/tools/packaging/kata-deploy/helm-chart/kata-deploy/values.yaml index a2aaf12579..418d7b759c 100644 --- a/tools/packaging/kata-deploy/helm-chart/kata-deploy/values.yaml +++ b/tools/packaging/kata-deploy/helm-chart/kata-deploy/values.yaml @@ -307,7 +307,7 @@ debug: false # Deploy the optional "devkit" debug guest extension and, per enabled shim, an # extra `kata--devkit` RuntimeClass wired to it. Pods on that class boot # with the extension cold-plugged, so the agent debug console drops into a rich -# shell (Alpine + apk + debug tools) overlaid on the read-only guest. +# shell (Ubuntu + apt + debug tools) overlaid on the read-only guest. # # Debugging aid: only effective with `debug: true`, ignored otherwise. Leave it # off in production and in confidential (CoCo/TEE) deployments. diff --git a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh index 792853b0a0..e57cf57722 100755 --- a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh +++ b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh @@ -741,35 +741,35 @@ EOF rm -rf "${extension_rootfs}" } -# Debug tools prebaked so they work offline; `apk add ` covers anything else -# inside the chroot. busybox-static is the static /bin/busybox.static that -# bootstraps the overlay/chroot from the shell-less guest base (see +# Debug tools prebaked so they work offline; `devkit-apt install ` covers +# anything else inside the chroot. busybox-static is the static /bin/busybox.static +# that bootstraps the overlay/chroot from the shell-less guest base (see # devkit-init.sh); pciutils and util-linux let the guest's devices and namespaces # be inspected from the chroot. Kept lean: heavier tools are pulled on demand. -readonly devkit_debug_packages="busybox-static bash ca-certificates strace ltrace iproute2 procps psmisc lsof tcpdump curl wget file less netcat-openbsd bind-tools pciutils util-linux" +readonly devkit_debug_packages="busybox-static bash ca-certificates strace ltrace iproute2 procps psmisc lsof tcpdump curl wget file less netcat-openbsd dnsutils pciutils util-linux" # Install the generic devkit debug extension image (erofs+verity): a self-contained -# minimal Alpine rootfs with debug tools prebaked. It ships no kata-agent, kernel +# minimal Ubuntu rootfs with debug tools prebaked. It ships no kata-agent, kernel # or driver userspace, so - unlike the monolithic images - it is assembled here # and handed to image_builder.sh (like install_image_coco_extension), never via # rootfs.sh. install_image_devkit_extension() { local component="rootfs-image-devkit-extension" - local branch ver mirror - branch="$(get_from_kata_deps ".externals.alpine.branch")" - ver="$(get_from_kata_deps ".externals.alpine.version")" - mirror="$(get_from_kata_deps ".externals.alpine.mirror")" - [[ -n "${branch}" ]] || die "externals.alpine.branch must be set in versions.yaml" - [[ -n "${ver}" ]] || die "externals.alpine.version must be set in versions.yaml" - [[ -n "${mirror}" ]] || mirror="https://dl-cdn.alpinelinux.org/alpine" + # Reuse the guest image's Ubuntu release (e.g. "noble") so the devkit matches + # the base userspace ABI (glibc) the guest ships. + local os_name os_version + os_name="$(get_from_kata_deps ".assets.image.architecture.${ARCH}.name")" + os_version="$(get_from_kata_deps ".assets.image.architecture.${ARCH}.version")" + [[ "${os_name}" == "ubuntu" ]] || die "devkit extension expects an ubuntu base, got '${os_name}' for ${ARCH}" + [[ -n "${os_version}" ]] || die "assets.image.architecture.${ARCH}.version must be set in versions.yaml" - # Self-contained (Alpine release + guest scripts), so key the cache on the - # Alpine version and the devkit source directory. + # Self-contained (Ubuntu release + guest scripts), so key the cache on the + # Ubuntu version and the devkit source directory. local devkit_last_commit devkit_last_commit="$(git -C "${repo_root_dir}" log -1 --abbrev=9 --pretty=format:"%h" \ -- tools/osbuilder/rootfs-builder/devkit 2>/dev/null || echo "unknown")" - latest_artefact="$(get_kata_version)-devkit-extension-${ver}-${devkit_last_commit}" + latest_artefact="$(get_kata_version)-devkit-extension-${os_version}-${devkit_last_commit}" latest_builder_image="" install_cached_tarball_component \ @@ -787,60 +787,61 @@ install_image_devkit_extension() { local extension_rootfs extension_rootfs="$(mktemp -d "${repo_root_dir}/.devkit-extension-rootfs.XXXX")" - # Alpine's arch naming matches ${ARCH} (x86_64/aarch64). - local tarball url - tarball="alpine-minirootfs-${ver}-${ARCH}.tar.gz" - url="${mirror}/${branch}/releases/${ARCH}/${tarball}" - info "Fetching Alpine minirootfs ${url}" - curl -fsSL -o "${extension_rootfs}/../${tarball}" "${url}" \ - || die "failed to download Alpine minirootfs ${url}" - tar -xzf "${extension_rootfs}/../${tarball}" -C "${extension_rootfs}" - rm -f "${extension_rootfs}/../${tarball}" - - # Pin repositories and seed a resolver so apk can fetch indexes and content. - mkdir -p "${extension_rootfs}/etc/apk" - cat > "${extension_rootfs}/etc/apk/repositories" <<-EOF - ${mirror}/${branch}/main - ${mirror}/${branch}/community + # Build an Ubuntu image with the debug toolset, then export its filesystem as + # the extension rootfs. Building the toolset via `docker build`/apt lets apt + # resolve dependencies normally (no chroot or --root gymnastics), and + # `docker export | tar` as the unprivileged build user yields a tree we own + # outright - no privileged sibling / chown dance needed. + info "Building devkit ubuntu rootfs (${os_name}:${os_version}) with debug toolset" + local build_tag="kata-devkit-ubuntu-${ARCH}:build" + docker build -t "${build_tag}" - <<-EOF || die "docker build for the devkit ubuntu rootfs failed" + FROM docker.io/library/ubuntu:${os_version} + ENV DEBIAN_FRONTEND=noninteractive + RUN apt-get update \ + && apt-get install -y --no-install-recommends ${devkit_debug_packages} \ + && rm -rf /var/lib/apt/lists/* EOF - cp -L /etc/resolv.conf "${extension_rootfs}/etc/resolv.conf" 2>/dev/null || true - # The build container is unprivileged, so a direct chroot is denied. Run apk - # in a privileged sibling container instead (Docker-in-Docker uses host paths, - # hence the repo-root temp dir): apk installs into --root but still runs - # package scripts chrooted, which needs the sibling's privileges. apk writes - # as root, so chown the tree back afterwards or the trim/cleanup and - # image_builder steps below hit "Permission denied". - info "Installing devkit debug toolset with apk" - # shellcheck disable=SC2086 - docker run --rm --privileged \ - -v "${extension_rootfs}:${extension_rootfs}" \ - "docker.io/library/alpine:${ver}" \ - sh -c "/sbin/apk add --no-cache --root '${extension_rootfs}' ${devkit_debug_packages} \ - && chown -R $(id -u):$(id -g) '${extension_rootfs}'" \ - || die "apk failed to install the devkit debug toolset" + local cid + cid="$(docker create "${build_tag}")" || die "docker create for the devkit ubuntu rootfs failed" + # Skip /dev: docker export carries device nodes the unprivileged tar cannot + # recreate (devkit-init mounts /dev at runtime anyway). + docker export "${cid}" | tar -C "${extension_rootfs}" --exclude='dev/*' -xf - \ + || { docker rm -f "${cid}" >/dev/null 2>&1 || true; die "exporting the devkit ubuntu rootfs failed"; } + docker rm -f "${cid}" >/dev/null 2>&1 || true + docker rmi -f "${build_tag}" >/dev/null 2>&1 || true + + # busybox-static ships a statically-linked busybox; expose it at the path + # devkit-init bootstraps from on the shell-less guest base. + local bb_src="" + local cand + for cand in usr/bin/busybox bin/busybox usr/sbin/busybox sbin/busybox; do + [[ -f "${extension_rootfs}/${cand}" ]] && { bb_src="${cand}"; break; } + done + [[ -n "${bb_src}" ]] || die "busybox-static not found in the devkit ubuntu rootfs" + cp -a "${extension_rootfs}/${bb_src}" "${extension_rootfs}/usr/bin/busybox.static" # Install the guest-side helper scripts and expose the debug console entry as - # ${DEVKIT}/bin/devkit-sh. Do NOT reuse ${DEVKIT}/bin/sh: that is Alpine's own - # /bin/sh -> busybox symlink, needed unclobbered inside the chroot. + # devkit-sh (a sibling symlink to devkit-enter; Ubuntu's /bin is a symlink to + # /usr/bin, so /run/kata-extensions/devkit/bin/devkit-sh resolves here). local devkit_src="${repo_root_dir}/tools/osbuilder/rootfs-builder/devkit" local script dest - for script in devkit-init.sh devkit-enter.sh devkit-apk.sh; do + for script in devkit-init.sh devkit-enter.sh devkit-apt.sh; do [[ -f "${devkit_src}/${script}" ]] || die "missing ${devkit_src}/${script}" dest="${script%.sh}" install -D -m0755 "${devkit_src}/${script}" "${extension_rootfs}/usr/bin/${dest}" done - mkdir -p "${extension_rootfs}/bin" - ln -sf ../usr/bin/devkit-enter "${extension_rootfs}/bin/devkit-sh" + ln -sf devkit-enter "${extension_rootfs}/usr/bin/devkit-sh" - # Trim what a debug rootfs does not need (man/doc/info, apk cache) and the - # seeded resolver (devkit-init re-seeds one at runtime). Root inode must be - # 0755 (mktemp makes it 0700), else the mount point is unsearchable non-root. + # Trim what a debug rootfs does not need (man/doc/info, apt lists/cache) and + # the docker-seeded resolver (devkit-init re-seeds one at runtime). Root inode + # must be 0755 (mktemp makes it 0700), else the mount point is unsearchable. rm -rf \ "${extension_rootfs}/usr/share/man"/* \ "${extension_rootfs}/usr/share/doc"/* \ "${extension_rootfs}/usr/share/info"/* \ - "${extension_rootfs}/var/cache/apk"/* \ + "${extension_rootfs}/var/lib/apt/lists"/* \ + "${extension_rootfs}/var/cache/apt"/* \ "${extension_rootfs}/etc/resolv.conf" \ 2>/dev/null || true chmod 0755 "${extension_rootfs}" diff --git a/versions.yaml b/versions.yaml index 6f0a3bba39..0b893ee72a 100644 --- a/versions.yaml +++ b/versions.yaml @@ -211,12 +211,6 @@ assets: externals: description: "Third-party projects used by the system" - alpine: - desc: "Alpine Linux minirootfs, base for the devkit debug extension" - branch: "v3.21" - version: "3.21.3" - mirror: "https://dl-cdn.alpinelinux.org/alpine" - nvrc: # yamllint disable-line rule:line-length desc: "The NVRC project provides a Rust binary that implements a simple init system for microVMs"