From 378c76a5268907aca43104f16e2acd641903df75 Mon Sep 17 00:00:00 2001 From: Marco Nenciarini Date: Thu, 29 Jan 2026 16:43:55 +0100 Subject: [PATCH] fix: resolve WAL archiving performance and memory issues (#746) The barman-cloud plugin experienced significant performance degradation and memory growth compared to the embedded solution. WAL archiving was noticeably slower and memory consumption grew over time. Root cause: The sidecar uses a read-only filesystem which prevents Python from creating bytecode at runtime. When Python finds missing or stale bytecode (.pyc files), it attempts to recompile on every invocation, causing high CPU usage and memory consumption. The previous approach pre-compiled bytecode in a separate base image, but the bytecode was marked as stale when copied between Docker stages, triggering runtime recompilation attempts. This change eliminates bytecode staleness by ensuring all Python bytecode is properly compiled in the final image before the sidecar starts. The image is now fully distroless and based on trixie (previously it was distroless-based but copied unnecessary files from the build stage), reducing size from 463MB to 270MB and package count from 188 to 35, while maintaining zero HIGH/CRITICAL vulnerabilities. Closes #656 Closes #711 Closes #735 Signed-off-by: Marco Nenciarini --- .github/workflows/barman-base-image.yml | 38 ----------- Taskfile.yml | 28 -------- containers/Dockerfile.barmanbase | 7 -- containers/Dockerfile.sidecar | 86 +++++++++++++++++++------ renovate.json5 | 6 -- 5 files changed, 65 insertions(+), 100 deletions(-) delete mode 100644 .github/workflows/barman-base-image.yml delete mode 100644 containers/Dockerfile.barmanbase diff --git a/.github/workflows/barman-base-image.yml b/.github/workflows/barman-base-image.yml deleted file mode 100644 index 2bf03db..0000000 --- a/.github/workflows/barman-base-image.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Barman Base Image -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * 0" - push: - branches: - - main - paths: - - 'containers/sidecar-requirements.txt' - -permissions: read-all - -jobs: - build: - runs-on: ubuntu-latest - permissions: - packages: write - contents: write - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Install QEMU static binaries - uses: docker/setup-qemu-action@v3 - - name: Install Task - uses: arduino/setup-task@v2 - - name: Install Dagger - env: - # renovate: datasource=github-tags depName=dagger/dagger versioning=semver - DAGGER_VERSION: 0.19.10 - run: | - curl -L https://dl.dagger.io/dagger/install.sh | BIN_DIR=$HOME/.local/bin sh - - name: Publish a barman-base - env: - REGISTRY_USER: ${{ github.actor }} - REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - run: | - task publish-barman-base diff --git a/Taskfile.yml b/Taskfile.yml index cfcc12b..4140607 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -381,34 +381,6 @@ tasks: build --dir . --file containers/Dockerfile.sidecar --platform linux/amd64 --platform linux/arm64 publish --ref {{.SIDECAR_IMAGE_NAME}} --tags {{.IMAGE_VERSION}} - publish-barman-base: - desc: Build and publish a barman-cloud base container image - vars: - BARMAN_BASE_IMAGE_NAME: ghcr.io/{{.GITHUB_REPOSITORY}}-base{{if not (hasPrefix "refs/heads/main" .GITHUB_REF)}}-testing{{end}} - BARMAN_VERSION: - sh: grep "^barman" containers/sidecar-requirements.in | sed -E 's/.*==([^ ]+)/\1/' - BUILD_DATE: - sh: date +"%Y%m%d%H%M" - requires: - # We expect this to run in a GitHub workflow, so we put a few GitHub-specific vars here - # to prevent running this task locally by accident. - vars: - - CI - - GITHUB_REPOSITORY - - GITHUB_REF - - GITHUB_REF_NAME - - REGISTRY_USER - - REGISTRY_PASSWORD - env: - # renovate: datasource=git-refs depName=docker lookupName=https://github.com/purpleclay/daggerverse currentValue=main - DAGGER_DOCKER_SHA: ee12c1a4a2630e194ec20c5a9959183e3a78c192 - cmds: - - > - dagger call -m github.com/purpleclay/daggerverse/docker@${DAGGER_DOCKER_SHA} - --registry ghcr.io --username $REGISTRY_USER --password env:REGISTRY_PASSWORD - build --dir . --file containers/Dockerfile.barmanbase --platform linux/amd64 --platform linux/arm64 - publish --ref {{.BARMAN_BASE_IMAGE_NAME}} --tags "{{.BARMAN_VERSION}}-{{.BUILD_DATE}}" - controller-gen: desc: Run controller-gen run: once diff --git a/containers/Dockerfile.barmanbase b/containers/Dockerfile.barmanbase deleted file mode 100644 index cc01748..0000000 --- a/containers/Dockerfile.barmanbase +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.13-slim-bookworm -COPY containers/sidecar-requirements.txt . -RUN apt-get update && \ - apt-get install -y postgresql-common build-essential && \ - /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \ - apt-get install -y libpq-dev && \ - pip install -r sidecar-requirements.txt diff --git a/containers/Dockerfile.sidecar b/containers/Dockerfile.sidecar index 7b42d32..536b130 100644 --- a/containers/Dockerfile.sidecar +++ b/containers/Dockerfile.sidecar @@ -10,7 +10,7 @@ ARG TARGETOS ARG TARGETARCH WORKDIR /workspace -# Copy the Go Modules manifests + COPY ../go.mod go.mod COPY ../go.sum go.sum # cache deps before building and copying source so that we don't need to re-download as much @@ -20,35 +20,73 @@ RUN go mod download ENV GOCACHE=/root/.cache/go-build ENV GOMODCACHE=/go/pkg/mod -# Copy the go source COPY ../cmd/manager/main.go cmd/manager/main.go COPY ../api/ api/ COPY ../internal/ internal/ -# Build -# the GOARCH has not a default value to allow the binary be built according to the host where the command -# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO -# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, -# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +# Build Go binary for target platform (TARGETOS/TARGETARCH) +# Docker BuildKit sets these based on --platform flag or defaults to the build host platform RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/manager/main.go -# Use plugin-barman-cloud-base to get the dependencies. -# pip will build everything inside /usr, so we copy every file into a new -# destination that will then be copied into the distroless container -FROM ghcr.io/cloudnative-pg/plugin-barman-cloud-base:3.17.0-202601131704 AS pythonbuilder -# Prepare a new /usr/ directory with the files we'll need in the final image -RUN mkdir /new-usr/ && \ - cp -r --parents /usr/local/lib/ /usr/lib/*-linux-gnu/ /usr/local/bin/ \ - /new-usr/ +# Build Python virtualenv with all dependencies +FROM debian:trixie-slim AS pythonbuilder +WORKDIR /build -# Joint process -# Now we put everything that was build from the origin into our -# distroless container -FROM gcr.io/distroless/python3-debian12:nonroot +# Install postgresql-common and setup pgdg repository first +RUN apt-get update && \ + apt-get install -y --no-install-recommends postgresql-common && \ + /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y + +# Install build dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + python3-venv \ + python3-dev \ + build-essential \ + libpq-dev \ + liblz4-dev \ + libsnappy-dev + +COPY containers/sidecar-requirements.txt . + +# Create virtualenv and install dependencies +RUN python3 -m venv /venv && \ + /venv/bin/pip install --upgrade pip setuptools wheel && \ + /venv/bin/pip install --no-cache-dir -r sidecar-requirements.txt + +# Download and extract runtime library packages and their dependencies +# Using apt-cache to automatically resolve dependencies, filtering out packages +# already present in the distroless base image. +# Distroless package list from: https://github.com/GoogleContainerTools/distroless/blob/main/base/config.bzl +# and https://github.com/GoogleContainerTools/distroless/blob/main/python3/config.bzl +RUN mkdir -p /dependencies /build/downloads && \ + cd /build/downloads && \ + DISTROLESS_PACKAGES="libc6 libssl3t64 libzstd1 zlib1g libgcc-s1 libstdc++6 \ + libbz2-1.0 libdb5.3t64 libexpat1 liblzma5 libsqlite3-0 libuuid1 \ + libncursesw6 libtinfo6 libcom-err2 libcrypt1 libgssapi-krb5-2 \ + libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libnsl2 \ + libreadline8t64 libtirpc3t64 libffi8 libpython3.13-minimal \ + libpython3.13-stdlib python3.13-minimal python3.13-venv" && \ + apt-cache depends --recurse --no-recommends --no-suggests \ + --no-conflicts --no-breaks --no-replaces --no-enhances \ + $DISTROLESS_PACKAGES 2>/dev/null | grep "^\w" | sort -u > /tmp/distroless.txt && \ + apt-cache depends --recurse --no-recommends --no-suggests \ + --no-conflicts --no-breaks --no-replaces --no-enhances \ + libpq5 liblz4-1 libsnappy1v5 2>/dev/null | grep "^\w" | sort -u | \ + grep -v -F -x -f /tmp/distroless.txt > /tmp/packages.txt && \ + apt-get download $(cat /tmp/packages.txt) && \ + for deb in *.deb; do \ + dpkg -x "$deb" /dependencies; \ + done + +# Final sidecar image using distroless base for minimal size and fewer packages +FROM gcr.io/distroless/python3-debian13:nonroot ENV SUMMARY="CloudNativePG Barman plugin" \ - DESCRIPTION="Container image that provides the barman-cloud sidecar" + DESCRIPTION="Container image that provides the barman-cloud sidecar" \ + PATH="/venv/bin:$PATH" LABEL summary="$SUMMARY" \ description="$DESCRIPTION" \ @@ -60,7 +98,13 @@ LABEL summary="$SUMMARY" \ version="" \ release="1" -COPY --from=pythonbuilder /new-usr/* /usr/ +COPY --from=pythonbuilder /venv /venv +COPY --from=pythonbuilder /dependencies/usr/lib /usr/lib COPY --from=gobuilder /workspace/manager /manager + +# Compile all Python bytecode as root to avoid runtime compilation +USER 0:0 +RUN ["/venv/bin/python3", "-c", "import sysconfig, compileall; compileall.compile_dir(sysconfig.get_path('stdlib'), quiet=1); compileall.compile_dir('/venv', quiet=1)"] + USER 26:26 ENTRYPOINT ["/manager"] diff --git a/renovate.json5 b/renovate.json5 index 29276e7..1aaa515 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -79,12 +79,6 @@ enabled: false, }, packageRules: [ - { - matchPackageNames: [ - 'ghcr.io/cloudnative-pg/plugin-barman-cloud-base', - ], - versioning: 'loose', - }, { matchDatasources: [ 'go',