Containerize local builds with rootless nerdctl
This commit is contained in:
parent
4aef12054e
commit
30fc02d79f
6 changed files with 216 additions and 31 deletions
45
README.md
Normal file
45
README.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Lazaret
|
||||||
|
|
||||||
|
Rootless gVisor sandbox for development tools and coding agents.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- Early development
|
||||||
|
- Source only; no binary releases
|
||||||
|
- Linux x86-64, kernel 6.9 or newer
|
||||||
|
|
||||||
|
## Host requirements
|
||||||
|
|
||||||
|
- Unprivileged user namespaces
|
||||||
|
- Contiguous subordinate UID and GID ranges of at least 65,536 IDs
|
||||||
|
- `/usr/bin/newuidmap`, `/usr/bin/newgidmap`, and `/usr/bin/slirp4netns`
|
||||||
|
- `/dev/fuse`, `fuse-overlayfs`, and `fusermount3`
|
||||||
|
- Rootless nerdctl with BuildKit
|
||||||
|
- `cmp`, `install`, and `sha256sum`
|
||||||
|
|
||||||
|
## Build and install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tools/install-to-host
|
||||||
|
```
|
||||||
|
|
||||||
|
The default destination is `~/.local/bin`. Pass `--install-dir DIRECTORY` to override it.
|
||||||
|
|
||||||
|
## Configure and run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p ~/.config/lazaret
|
||||||
|
cp docs/example.toml ~/.config/lazaret/config.toml
|
||||||
|
lazaret default
|
||||||
|
lazaret default -- COMMAND ARGUMENT...
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit the example network allowlists and persistent volume paths before use.
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
- [Product specification](docs/spec/lazaret.md)
|
||||||
|
- [Example configuration](docs/example.toml)
|
||||||
|
- [Verification entry point](tools/verify)
|
||||||
|
- [Apache License 2.0](LICENSE)
|
||||||
|
- [Third-party notices](THIRD_PARTY_NOTICES.md)
|
||||||
|
|
@ -448,6 +448,27 @@ class VerificationTests(unittest.TestCase):
|
||||||
(ROOT / "patches/gvisor-9f8f6c917-MODULE.bazel.lock").is_file()
|
(ROOT / "patches/gvisor-9f8f6c917-MODULE.bazel.lock").is_file()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_canonical_build_uses_one_unprivileged_nerdctl_container(self) -> None:
|
||||||
|
build = (ROOT / "tools/build").read_text()
|
||||||
|
containerfile = (ROOT / "tools/container/Containerfile").read_text()
|
||||||
|
installer = (ROOT / "tools/install-to-host").read_text()
|
||||||
|
materializer = (ROOT / "tools/materialize-runsc").read_text()
|
||||||
|
|
||||||
|
self.assertIn("nerdctl build", build)
|
||||||
|
self.assertIn("nerdctl run --rm", build)
|
||||||
|
self.assertIn("tools/materialize-runsc --build", build)
|
||||||
|
self.assertIn("cargo build", build)
|
||||||
|
self.assertIn("GIT_CEILING_DIRECTORIES=/workspace", build)
|
||||||
|
self.assertNotIn("--privileged", build)
|
||||||
|
self.assertNotIn("docker.sock", build)
|
||||||
|
self.assertIn("rust:1.97.0-bookworm@sha256:", containerfile)
|
||||||
|
self.assertIn("ARG BAZEL_VERSION=8.3.1", containerfile)
|
||||||
|
self.assertIn("tools/build", installer)
|
||||||
|
self.assertNotIn("cargo build", installer)
|
||||||
|
self.assertNotIn("readelf", installer)
|
||||||
|
self.assertIn("bazel build", materializer)
|
||||||
|
self.assertNotIn("make -C", materializer)
|
||||||
|
|
||||||
def test_workspace_tests_use_nextest_and_cargo_retains_doctests(self) -> None:
|
def test_workspace_tests_use_nextest_and_cargo_retains_doctests(self) -> None:
|
||||||
source = (ROOT / "tools/lazaret_verify.py").read_text()
|
source = (ROOT / "tools/lazaret_verify.py").read_text()
|
||||||
expected = [
|
expected = [
|
||||||
|
|
|
||||||
67
tools/build
Executable file
67
tools/build
Executable file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
containerfile="$root/tools/container/Containerfile"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'build: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == --inside-container && $# -eq 1 ]]; then
|
||||||
|
[[ "${LAZARET_BUILD_CONTAINER:-}" == 1 ]] || fail 'container marker is absent'
|
||||||
|
cd "$root"
|
||||||
|
tools/materialize-fuser
|
||||||
|
tools/materialize-runsc --build
|
||||||
|
cargo build \
|
||||||
|
--release \
|
||||||
|
--locked \
|
||||||
|
--package lazaret-client \
|
||||||
|
--package lazaret-host-coordinator \
|
||||||
|
--package lazaret-escalation-client \
|
||||||
|
--bins
|
||||||
|
env RUSTFLAGS="-C relocation-model=static -C link-arg=-no-pie" cargo build \
|
||||||
|
--release \
|
||||||
|
--locked \
|
||||||
|
--target x86_64-unknown-linux-musl \
|
||||||
|
--package lazaret-sandbox-init \
|
||||||
|
--bin lazaret-sandbox-init
|
||||||
|
sandbox_init="$CARGO_TARGET_DIR/x86_64-unknown-linux-musl/release/lazaret-sandbox-init"
|
||||||
|
if readelf -l "$sandbox_init" | grep -q 'Requesting program interpreter' ||
|
||||||
|
! readelf -d "$sandbox_init" 2>&1 | grep -q 'There is no dynamic section'; then
|
||||||
|
fail 'sandbox init is dynamically linked'
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
elif [[ $# -ne 0 ]]; then
|
||||||
|
printf '%s\n' 'usage: tools/build' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
for command in nerdctl sha256sum; do
|
||||||
|
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$root/.lazaret"
|
||||||
|
containerfile_digest="$(sha256sum "$containerfile")"
|
||||||
|
containerfile_digest="${containerfile_digest%% *}"
|
||||||
|
image="localhost/lazaret-builder:${containerfile_digest:0:16}"
|
||||||
|
proxy_options=()
|
||||||
|
for variable in ALL_PROXY HTTP_PROXY HTTPS_PROXY NO_PROXY all_proxy http_proxy https_proxy no_proxy; do
|
||||||
|
if [[ -v $variable ]]; then
|
||||||
|
proxy_options+=(--env "$variable")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
nerdctl build --tag "$image" --file "$containerfile" "$root/tools/container"
|
||||||
|
nerdctl run --rm \
|
||||||
|
--mount "type=bind,src=$root,dst=/workspace,ro" \
|
||||||
|
--mount "type=bind,src=$root/.lazaret,dst=/workspace/.lazaret" \
|
||||||
|
--workdir /workspace \
|
||||||
|
--env CARGO_HOME=/workspace/.lazaret/cache/cargo \
|
||||||
|
--env CARGO_TARGET_DIR=/workspace/.lazaret/target \
|
||||||
|
--env GIT_CEILING_DIRECTORIES=/workspace \
|
||||||
|
--env HOME=/workspace/.lazaret/home \
|
||||||
|
--env LAZARET_BUILD_CONTAINER=1 \
|
||||||
|
"${proxy_options[@]}" \
|
||||||
|
"$image" \
|
||||||
|
tools/build --inside-container
|
||||||
49
tools/container/Containerfile
Normal file
49
tools/container/Containerfile
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
FROM docker.io/library/rust:1.97.0-bookworm@sha256:8fa55b2f3ddf97471ab6a767bfa3f37e6bad0986ba823e75fea57e2a2a5c3073
|
||||||
|
|
||||||
|
ARG BAZEL_VERSION=8.3.1
|
||||||
|
ARG BAZEL_SHA256=17247e8a84245f59d3bc633d0cfe0a840992a7760a11af1a30012d03da31604c
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
binutils \
|
||||||
|
build-essential \
|
||||||
|
busybox-static \
|
||||||
|
ca-certificates \
|
||||||
|
clang \
|
||||||
|
cmake \
|
||||||
|
crossbuild-essential-amd64 \
|
||||||
|
crossbuild-essential-arm64 \
|
||||||
|
curl \
|
||||||
|
diffutils \
|
||||||
|
erofs-utils \
|
||||||
|
git \
|
||||||
|
iproute2 \
|
||||||
|
iptables \
|
||||||
|
kmod \
|
||||||
|
libbpf-dev \
|
||||||
|
libc6-dev-i386 \
|
||||||
|
libffi-dev \
|
||||||
|
libnuma-dev \
|
||||||
|
libssl-dev \
|
||||||
|
musl-tools \
|
||||||
|
netcat-openbsd \
|
||||||
|
openjdk-17-jdk-headless \
|
||||||
|
patch \
|
||||||
|
pkg-config \
|
||||||
|
python-is-python3 \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-setuptools \
|
||||||
|
qemu-user-static \
|
||||||
|
unzip \
|
||||||
|
zip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN curl --fail --location \
|
||||||
|
"https://releases.bazel.build/${BAZEL_VERSION}/release/bazel-${BAZEL_VERSION}-linux-x86_64" \
|
||||||
|
--output /usr/local/bin/bazel \
|
||||||
|
&& printf '%s %s\n' "$BAZEL_SHA256" /usr/local/bin/bazel | sha256sum --check - \
|
||||||
|
&& chmod 0755 /usr/local/bin/bazel \
|
||||||
|
&& rustup target add x86_64-unknown-linux-musl \
|
||||||
|
&& rustup component add clippy rustfmt
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
@ -18,7 +18,7 @@ elif [ "$#" -ne 0 ]; then
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for command in cargo cmp install readelf; do
|
for command in cmp install; do
|
||||||
command -v "$command" >/dev/null 2>&1 || {
|
command -v "$command" >/dev/null 2>&1 || {
|
||||||
printf '%s\n' "install-to-host: $command is required" >&2
|
printf '%s\n' "install-to-host: $command is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -26,37 +26,15 @@ for command in cargo cmp install readelf; do
|
||||||
done
|
done
|
||||||
|
|
||||||
cd "$root"
|
cd "$root"
|
||||||
tools/materialize-fuser
|
tools/build
|
||||||
tools/materialize-runsc --build
|
|
||||||
cargo build \
|
|
||||||
--release \
|
|
||||||
--locked \
|
|
||||||
--package lazaret-client \
|
|
||||||
--package lazaret-host-coordinator \
|
|
||||||
--package lazaret-escalation-client \
|
|
||||||
--bins
|
|
||||||
env RUSTFLAGS="-C relocation-model=static -C link-arg=-no-pie" cargo build \
|
|
||||||
--release \
|
|
||||||
--locked \
|
|
||||||
--target x86_64-unknown-linux-musl \
|
|
||||||
--package lazaret-sandbox-init \
|
|
||||||
--bin lazaret-sandbox-init
|
|
||||||
|
|
||||||
target_dir="${CARGO_TARGET_DIR:-target}"
|
target_dir="$root/.lazaret/target"
|
||||||
if [[ "$target_dir" != /* ]]; then
|
|
||||||
target_dir="$root/$target_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
install -d -m 755 "$install_dir"
|
install -d -m 755 "$install_dir"
|
||||||
for executable in lazaret lazaret-host-coordinator lazaret-escalation-client; do
|
for executable in lazaret lazaret-host-coordinator lazaret-escalation-client; do
|
||||||
install -m 755 "$target_dir/release/$executable" "$install_dir/$executable"
|
install -m 755 "$target_dir/release/$executable" "$install_dir/$executable"
|
||||||
done
|
done
|
||||||
sandbox_init="$target_dir/x86_64-unknown-linux-musl/release/lazaret-sandbox-init"
|
sandbox_init="$target_dir/x86_64-unknown-linux-musl/release/lazaret-sandbox-init"
|
||||||
if readelf -l "$sandbox_init" | grep -q 'Requesting program interpreter' ||
|
|
||||||
! readelf -d "$sandbox_init" 2>&1 | grep -q 'There is no dynamic section'; then
|
|
||||||
printf '%s\n' "install-to-host: sandbox init is dynamically linked" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
install -m 755 "$sandbox_init" "$install_dir/lazaret-sandbox-init"
|
install -m 755 "$sandbox_init" "$install_dir/lazaret-sandbox-init"
|
||||||
|
|
||||||
runsc_build_id=9e711e005f13402e728a0c3ada3fdc67a38918da31f720e5be1006edc4272a51
|
runsc_build_id=9e711e005f13402e728a0c3ada3fdc67a38918da31f720e5be1006edc4272a51
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fail() {
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in curl make patch sha256sum tar; do
|
for command in curl patch sha256sum tar; do
|
||||||
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
|
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
@ -92,14 +92,39 @@ elif [[ "$(tr -d '\n' <"$source/.lazaret-materialization")" != "$stamp" ]]; then
|
||||||
fail 'materialized source identity mismatch'
|
fail 'materialized source identity mismatch'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$mode" != source ]]; then
|
||||||
|
[[ "${LAZARET_BUILD_CONTAINER:-}" == 1 ]] || fail 'build and test modes require tools/build'
|
||||||
|
command -v bazel >/dev/null 2>&1 || fail 'bazel is required'
|
||||||
|
[[ "$(bazel --version)" == "bazel $bazel_version" ]] || fail 'Bazel version mismatch'
|
||||||
|
fi
|
||||||
if [[ "$mode" == test ]]; then
|
if [[ "$mode" == test ]]; then
|
||||||
make -C "$source" test \
|
(
|
||||||
TARGETS='//pkg/sentry/fsimpl/gofer:gofer_test //runsc/fsgofer:lisafs_test //test/syscalls:socket_ip_tcp_generic_loopback_test_native //test/syscalls:socket_ip_tcp_generic_loopback_test_runsc_systrap_hostnet'
|
cd "$source"
|
||||||
make -C "$source" test OPTIONS='--test_filter=TestProcessStateIsTerminal' \
|
bazel test \
|
||||||
TARGETS=//runsc/container:container_test
|
--color=no \
|
||||||
|
--curses=no \
|
||||||
|
--incompatible_sandbox_hermetic_tmp=false \
|
||||||
|
--keep_going \
|
||||||
|
--strip=never \
|
||||||
|
--test_output=errors \
|
||||||
|
--verbose_failures=true \
|
||||||
|
//pkg/sentry/fsimpl/gofer:gofer_test \
|
||||||
|
//runsc/fsgofer:lisafs_test \
|
||||||
|
//test/syscalls:socket_ip_tcp_generic_loopback_test_native \
|
||||||
|
//test/syscalls:socket_ip_tcp_generic_loopback_test_runsc_systrap_hostnet
|
||||||
|
bazel test \
|
||||||
|
--color=no \
|
||||||
|
--curses=no \
|
||||||
|
--incompatible_sandbox_hermetic_tmp=false \
|
||||||
|
--strip=never \
|
||||||
|
--test_filter=TestProcessStateIsTerminal \
|
||||||
|
--test_output=errors \
|
||||||
|
--verbose_failures=true \
|
||||||
|
//runsc/container:container_test
|
||||||
|
)
|
||||||
fi
|
fi
|
||||||
if [[ "$mode" != source ]]; then
|
if [[ "$mode" != source ]]; then
|
||||||
make -C "$source" build TARGETS=//runsc:runsc
|
(cd "$source" && bazel build --color=no --curses=no //runsc:runsc)
|
||||||
built="$source/bazel-bin/runsc/runsc_/runsc"
|
built="$source/bazel-bin/runsc/runsc_/runsc"
|
||||||
[[ -x "$built" ]] || fail 'canonical build did not produce runsc'
|
[[ -x "$built" ]] || fail 'canonical build did not produce runsc'
|
||||||
version_output="$($built --version 2>&1)"
|
version_output="$($built --version 2>&1)"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue