Skip to content

Commit e3acf8d

Browse files
committed
tests: add test for pivot_root in initramfs support
The only way to run in the proper initramfs is to start a VM using a custom initrd that runs runc. This should be a fairly reasonable smoke-test that matches what minikube and kata do. Unfortunately, running the right qemu for the native architecture on various distros is a little different, so we need a helper function to get it to work on both Debian and AlmaLinux. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 562c89a commit e3acf8d

File tree

6 files changed

+168
-6
lines changed

6 files changed

+168
-6
lines changed

.cirrus.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ task:
7979
CIRRUS_WORKING_DIR: /home/runc
8080
GO_VERSION: "1.23"
8181
BATS_VERSION: "v1.9.0"
82-
RPMS: gcc git iptables jq glibc-static libseccomp-devel make criu fuse-sshfs container-selinux
82+
RPMS: gcc git iptables jq qemu-kvm glibc-static libseccomp-devel make criu fuse-sshfs container-selinux
8383
# yamllint disable rule:key-duplicates
8484
matrix:
8585
DISTRO: almalinux-8

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ jobs:
120120
- name: install deps
121121
run: |
122122
sudo apt update
123-
sudo apt -y install libseccomp-dev sshfs uidmap
123+
sudo apt -y install cpio libseccomp-dev qemu-kvm sshfs uidmap
124124
125125
- name: install CRIU
126126
if: ${{ matrix.criu == '' }}

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \
1616
criu \
1717
gcc \
1818
gcc-multilib \
19+
cpio \
1920
curl \
2021
gawk \
2122
gperf \
@@ -24,6 +25,7 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \
2425
kmod \
2526
pkg-config \
2627
python3-minimal \
28+
qemu-kvm \
2729
sshfs \
2830
sudo \
2931
uidmap \

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ localunittest: all
172172
integration: runcimage
173173
$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
174174
-t --privileged --rm \
175+
-v /boot:/boot:ro \
175176
-v /lib/modules:/lib/modules:ro \
176177
-v $(CURDIR):/go/src/$(PROJECT) \
177178
$(RUNC_IMAGE) make localintegration TESTPATH="$(TESTPATH)"

tests/integration/helpers.bash

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ eval "$IMAGES"
1313
unset IMAGES
1414

1515
: "${RUNC:="${INTEGRATION_ROOT}/../../runc"}"
16+
: "${RUNC_STATIC:="${INTEGRATION_ROOT}/../../runc.static"}"
1617
RECVTTY="${INTEGRATION_ROOT}/../../tests/cmd/recvtty/recvtty"
1718
SD_HELPER="${INTEGRATION_ROOT}/../../tests/cmd/sd-helper/sd-helper"
1819
SECCOMP_AGENT="${INTEGRATION_ROOT}/../../tests/cmd/seccompagent/seccompagent"
@@ -39,18 +40,25 @@ ARCH=$(uname -m)
3940
# Seccomp agent socket.
4041
SECCCOMP_AGENT_SOCKET="$BATS_TMPDIR/seccomp-agent.sock"
4142

42-
# Wrapper for runc.
43-
function runc() {
44-
run __runc "$@"
43+
function sane_run() {
44+
local cmd="$1"
45+
shift
46+
47+
run "$cmd" "$@"
4548

4649
# Some debug information to make life easier. bats will only print it if the
4750
# test failed, in which case the output is useful.
4851
# shellcheck disable=SC2154
49-
echo "$(basename "$RUNC") $* (status=$status):" >&2
52+
echo "$cmd $* (status=$status):" >&2
5053
# shellcheck disable=SC2154
5154
echo "$output" >&2
5255
}
5356

57+
# Wrapper for runc.
58+
function runc() {
59+
sane_run __runc "$@"
60+
}
61+
5462
# Raw wrapper for runc.
5563
function __runc() {
5664
"$RUNC" ${RUNC_USE_SYSTEMD+--systemd-cgroup} \

tests/integration/initramfs.bats

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
function setup() {
6+
INITRAMFS_ROOT="$(mktemp -d "$BATS_RUN_TMPDIR/runc-initramfs.XXXXXX")"
7+
}
8+
9+
function teardown() {
10+
[ -v INITRAMFS_ROOT ] && rm -rf "$INITRAMFS_ROOT"
11+
}
12+
13+
function find_vmlinuz() {
14+
shopt -s nullglob
15+
candidates=(
16+
/boot/vmlinuz
17+
/boot/vmlinuz-$(uname -r)*
18+
/usr/lib*/modules/$(uname -r)/vmlinu* # vmlinuz / vmlinux.{gz,xz,*}
19+
)
20+
shopt -u nullglob
21+
22+
for candidate in "${candidates[@]}"; do
23+
[ -e "$candidate" ] || continue
24+
echo "$candidate"
25+
return 0
26+
done
27+
28+
skip "could not find host vmlinuz kernel"
29+
}
30+
31+
function qemu_native() {
32+
# Different distributions put qemu-kvm in different locations and with
33+
# different names. Debian and Ubuntu have a "kvm" binary, while AlmaLinux
34+
# has /usr/libexec/qemu-kvm.
35+
qemu_candidates=("kvm" "qemu-kvm" "/usr/libexec/qemu-kvm")
36+
37+
qemu_binary=
38+
for candidate in "${qemu_candidates[@]}"; do
39+
"$candidate" -help &>/dev/null || continue
40+
qemu_binary="$candidate"
41+
break
42+
done
43+
# TODO: Maybe we should also try to call qemu-system-FOO for the current
44+
# architecture if qemu-kvm is missing?
45+
[ -n "$qemu_binary" ] || skip "could not find qemu-kvm binary"
46+
47+
machine_args=()
48+
case "$(go env GOARCH)" in
49+
386 | amd64)
50+
machine_args=("-machine" "pc")
51+
;;
52+
arm | arm64)
53+
machine_args=("-machine" "virt")
54+
;;
55+
*)
56+
echo "could not figure out -machine argument for qemu -- using default" >&2
57+
;;
58+
esac
59+
60+
sane_run "$qemu_binary" "${machine_args[@]}" "$@"
61+
if [ "$status" -ne 0 ]; then
62+
# To help with debugging, output the set of valid machine values.
63+
"$qemu_binary" -machine help >&2
64+
fi
65+
}
66+
67+
@test "runc run [initramfs + pivot_root]" {
68+
requires root
69+
70+
KERNEL="$(find_vmlinuz)"
71+
72+
# Configure our minimal initrd.
73+
mkdir -p "$INITRAMFS_ROOT/initrd"
74+
pushd "$INITRAMFS_ROOT/initrd"
75+
76+
# Use busybox as a base for our initrd.
77+
tar --exclude './dev/*' -xf "$BUSYBOX_IMAGE"
78+
# Make sure that "sh" and "poweroff" are installed, otherwise qemu will
79+
# boot loop when init stops.
80+
[ -x ./bin/sh ] || skip "busybox image is missing /bin/sh"
81+
[ -x ./bin/poweroff ] || skip "busybox image is missing /bin/poweroff"
82+
83+
# Copy the runc binary into the container. In theory we would prefer to
84+
# copy a static binary, but some distros (like openSUSE) don't ship
85+
# libseccomp-static so requiring a static build for any integration test
86+
# run wouldn't work. Instead, we copy all of the library dependencies into
87+
# the rootfs (note that we also have to copy ld-linux-*.so because runc was
88+
# probably built with a newer glibc than the one in our busybox image.
89+
cp "$RUNC" ./bin/runc
90+
readarray -t runclibs \
91+
<<<"$(ldd "$RUNC" | grep -Eo '/[^ ]*lib[^ ]*.so.[^ ]*')"
92+
mkdir -p ./lib{,64}
93+
cp -vt ./lib/ "${runclibs[@]}"
94+
cp -vt ./lib64/ "${runclibs[@]}"
95+
96+
# Create a container bundle using the same busybox image.
97+
mkdir -p ./run/bundle
98+
pushd ./run/bundle
99+
mkdir -p rootfs
100+
tar --exclude './dev/*' -C rootfs -xf "$BUSYBOX_IMAGE"
101+
runc spec
102+
update_config '.process.args = ["/bin/echo", "hello from inside the container"]'
103+
popd
104+
105+
# Build a custom /init script.
106+
cat >./init <<-EOF
107+
#!/bin/sh
108+
109+
set -x
110+
echo "==START INIT SCRIPT=="
111+
112+
mkdir -p /proc /sys
113+
mount -t proc proc /proc
114+
mkdir -p /sys
115+
mount -t sysfs sysfs /sys
116+
117+
mkdir -p /sys/fs/cgroup
118+
mount -t cgroup2 cgroup2 /sys/fs/cgroup
119+
120+
mkdir -p /tmp
121+
mount -t tmpfs tmpfs /tmp
122+
123+
mkdir -p /dev
124+
mount -t devtmpfs devtmpfs /dev
125+
mkdir -p /dev/pts
126+
mount -t devpts -o newinstance devpts /dev/pts
127+
mkdir -p /dev/shm
128+
mount --bind /tmp /dev/shm
129+
130+
runc run -b /run/bundle ctr
131+
132+
echo "==END INIT SCRIPT=="
133+
poweroff -f
134+
EOF
135+
chmod +x ./init
136+
137+
find . | cpio -o -H newc >"$INITRAMFS_ROOT/initrd.cpio"
138+
popd
139+
140+
# Now we can just run the image (use qemu-kvm so that we run on the same
141+
# architecture as the host system). We can just reuse the host kernel.
142+
qemu_native \
143+
-initrd "$INITRAMFS_ROOT/initrd.cpio" \
144+
-kernel "$KERNEL" \
145+
-m 512M \
146+
-nographic -append console=ttyS0
147+
[ "$status" -eq 0 ]
148+
[[ "$output" = *"==START INIT SCRIPT=="* ]]
149+
[[ "$output" = *"hello from inside the container"* ]]
150+
[[ "$output" = *"==END INIT SCRIPT=="* ]]
151+
}

0 commit comments

Comments
 (0)