Skip to content

feat: optimize binary size, crun, zstd compression, online/offline build variants#117

Open
nova8-technologies wants to merge 1 commit intoportainer:developfrom
nova8-technologies:feat/optimize-binary-size
Open

feat: optimize binary size, crun, zstd compression, online/offline build variants#117
nova8-technologies wants to merge 1 commit intoportainer:developfrom
nova8-technologies:feat/optimize-binary-size

Conversation

@nova8-technologies
Copy link
Contributor

Description

Reduces KubeSolo binary size by ~160–180 MB for the default (online) build variant through three changes:

Phase 1: Replace runc with crun

Replace runc (~10 MB) with crun (~1 MB) as the OCI runtime. crun is a lightweight C-based runtime that is fully OCI-compliant and used as the default in Podman/Buildah. It supports cgroupv2 natively.

Note: crun does not publish pre-built 32-bit ARM (armhf) binaries. The download-deps.sh script will fail early with a clear error for GOARCH=arm.

Phase 2: zstd-compress embedded binaries

Compress containerd-shim-runc-v2, crun, and CNI plugin binaries with zstd -19 before go:embed. Binaries are decompressed at extraction time using github.com/klauspost/compress/zstd (already an indirect dependency, promoted to direct).

Phase 3: Online/offline build variants via build tags

Split into two build modes:

Variant Build Tag Embedded Images Runtime Behavior
Online (default) none CoreDNS + pause only Portainer Agent + local-path-provisioner pulled via client.Pull()
Offline -tags offline All images Air-gapped deployments, no network needed

The importImage() function now falls back to client.Pull() when an embedded image file is empty or missing, making the online variant seamless.

Build Commands

# Online build (default, ~160-180 MB smaller)
make build GOOS=linux GOARCH=amd64

# Offline build (all images embedded, air-gapped)
make build-offline GOOS=linux GOARCH=amd64

# Download deps for offline
make deps-offline

Files Changed

File Change
Makefile Add build-offline and deps-offline targets
build/download-deps.sh crun download, zstd compression, --offline flag
cmd/kubesolo/main.go RuncBinaryFileCrunBinaryFile
go.mod Promote klauspost/compress to direct dependency
internal/core/embedded/embedded.go .zst suffixes, remove always-embedded optional images
internal/core/embedded/embedded_riscv64.go Same .zst changes for riscv64
internal/core/embedded/embedded_images_offline.go New embed optional images with //go:build offline
internal/core/embedded/embedded_images_offline_riscv64.go New offline riscv64 (no portainer-agent)
internal/core/embedded/embedded_images_online.go New empty vars for online build
internal/core/embedded/load.go runcBinarycrunBinary
internal/runtime/filesystem/binary.go zstd decompression in ExtractBinary()
pkg/runtime/containerd/config.go Runtime name runccrun, cgroupv2 detection
pkg/runtime/containerd/image.go Fallback to client.Pull() for missing images
pkg/runtime/containerd/service.go runcBinaryFilecrunBinaryFile
types/types.go RuncBinaryFileCrunBinaryFile

Testing

  • Online build: verified CoreDNS/pause load from embedded, portainer-agent/local-path-provisioner pulled at runtime
  • Offline build: verified all images load from embedded tar.gz files
  • Verified zstd decompression produces identical binaries to originals
  • Tested on amd64 and arm64

…ine builds

Phase 1: Replace runc (~10MB) with crun (~1MB) as OCI runtime.
Phase 2: zstd-compress embedded binaries (shim, crun, CNI plugins) before
         go:embed, decompress at extraction time via klauspost/compress/zstd.
Phase 3: Split into online/offline build variants via build tags.
         Online (default): CoreDNS + pause embedded, Portainer Agent and
         local-path-provisioner pulled at runtime via client.Pull() fallback.
         Offline (-tags offline): All OCI images embedded for air-gapped
         deployments.

Estimated savings: ~160-180MB for the online variant.

Ref: nova8os#103
Amp-Thread-ID: https://ampcode.com/threads/T-019cc917-a21f-70be-b5b8-8bd5ab39a099
Co-authored-by: Amp <[email protected]>
@stevensbkang stevensbkang requested review from stevensbkang and removed request for deviantony March 16, 2026 20:38
Copy link
Member

@stevensbkang stevensbkang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you please consider the comment?

RUNC_ARCH=${ARCH}
# Download crun - add error checking
# crun does not publish 32-bit ARM binaries; fail early for unsupported architectures
if [ "${ARCH}" = "arm" ]; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We encountered the exact same limitation for containerd artefacts and ended up building our own https://github.com/portainer/kubesolo/blob/develop/build/containerd.Dockerfile.

I have done some testing and the following seems to do the job:

# crun build for arm/v7 using Debian Linux (glibc)
#
# Strategy: Build natively inside an arm/v7 Debian container via QEMU.
# This produces a glibc-linked binary that matches the current installer
# expectations for linux-arm artifacts.
#
# Build with: docker build --platform linux/arm/v7 -f ./build/crun.Dockerfile -t crun-arm32-builder .
# Extract with:
#   docker create --name crun-extract crun-arm32-builder noop
#   docker cp crun-extract:/crun ./internal/core/embedded/bin/crun
#   docker rm crun-extract

FROM --platform=linux/arm/v7 debian:12-slim AS builder

ARG CRUN_VERSION=1.26

# Install build dependencies
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        autoconf \
        automake \
        build-essential \
        ca-certificates \
        git \
        go-md2man \
        gperf \
        libcap-dev \
        libseccomp-dev \
        libsystemd-dev \
        libtool \
        linux-libc-dev \
        pkg-config \
        python3 \
        && rm -rf /var/lib/apt/lists/*

# Clone crun repository
RUN git clone --recursive https://github.com/containers/crun.git /src/crun
WORKDIR /src/crun

# Checkout specific version
RUN git checkout ${CRUN_VERSION} && \
    git submodule update --init --recursive

# Generate configure script
RUN ./autogen.sh

# Configure for glibc build with systemd support to match upstream features
RUN ./configure \
    --enable-systemd \
    --enable-embedded-yajl \
    --with-cap \
    --with-seccomp

# Build
RUN make -j"$(nproc)"

# Package output
RUN mkdir -p /output/bin && \
    cp crun /output/bin/ && \
    strip /output/bin/crun

# Final stage: copy the built binary to a clean image
FROM scratch
COPY --from=0 /output/bin/crun /

Testing on a arm/v7 RPi:

root@pi2b:~# dpkg --print-architecture
armhf
root@pi2b:~# uname -m
armv7l
root@pi2b:/tmp# ./crun --version
crun version 1.26
commit: 3241e671f92c33b0c003cd7de319e4f32add6231
rundir: /run/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +YAJL

Would you be able to add this to the PR, please? I will take care of the release to produce offline vs online in a separate PR 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants