diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 164b12b2..20375148 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,7 @@ concurrency: permissions: contents: write + packages: write jobs: build-and-release: @@ -30,7 +31,13 @@ jobs: # - rust toolchain matching rust-toolchain.toml (rustup default ) # - gh CLI (`apt install gh` via cli.github.com apt repo) # - build deps: clang llvm libclang-dev build-essential pkg-config - # libssl-dev libudev-dev + # libudev-dev + # (libssl-dev no longer needed — bin/gravity_node and bin/gravity_cli + # both vendor OpenSSL via `openssl = { features = ["vendored"] }`, + # so openssl-sys self-compiles. Perl is required for the vendored + # OpenSSL Configure script; usually present already on Debian.) + # - docker (BuildKit enabled) — used by the release docker image step + # to wrap the native binary into ghcr.io/galxe/gravity_node:$TAG timeout-minutes: 180 env: RUSTFLAGS: "--cfg tokio_unstable" @@ -100,6 +107,34 @@ jobs: if-no-files-found: error retention-days: 7 + - name: Build gravity_node docker image + # Uses the `runtime-host-binary` target of docker/gravity_node/Dockerfile, + # which COPYs the binary the previous cargo step just produced. BuildKit + # stage pruning skips the `builder` stage entirely — no double compile. + # Runs on both PR (build-only, no push) and tag pushes. + # + # Note: the repo's .dockerignore excludes `target/` from the build + # context, so we stage the binary into docker/gravity_node/bin/ (a path + # that IS in the context) before invoking `docker build`. This matches + # the pre-existing pattern used by regression/pfn_chain_stress/run.sh. + shell: bash + env: + IMAGE_REF: gravity_node:build-${{ github.sha }} + run: | + set -euo pipefail + mkdir -p docker/gravity_node/bin + cp -f target/quick-release/gravity_node docker/gravity_node/bin/gravity_node + DOCKER_BUILDKIT=1 docker build \ + --target runtime-host-binary \ + --build-arg HOST_BINARY=docker/gravity_node/bin/gravity_node \ + -t "${IMAGE_REF}" \ + -f docker/gravity_node/Dockerfile . + echo "--- image size ---" + docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep "^${IMAGE_REF}" + # Don't leave the binary in the repo working dir after the build — + # the runner reuses the checkout between jobs. + rm -f docker/gravity_node/bin/gravity_node + - name: Resolve tag id: tag if: github.event_name != 'pull_request' @@ -133,3 +168,27 @@ jobs: "$ASSETS_DIR/gravity_cli" \ "$ASSETS_DIR/gravity_cli.sha256" \ --clobber + + - name: Login to ghcr.io + if: github.event_name != 'pull_request' + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + echo "${GH_TOKEN}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin + + - name: Tag and push docker image to ghcr.io + if: github.event_name != 'pull_request' + shell: bash + env: + TAG: ${{ steps.tag.outputs.tag }} + BUILT_IMAGE: gravity_node:build-${{ github.sha }} + run: | + set -euo pipefail + # GHCR namespaces are lowercase; convert the github org name to be safe. + OWNER_LC="$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')" + IMAGE="ghcr.io/${OWNER_LC}/gravity_node:${TAG}" + docker tag "${BUILT_IMAGE}" "${IMAGE}" + docker push "${IMAGE}" + echo "pushed: ${IMAGE}" diff --git a/Cargo.lock b/Cargo.lock index 01d97b0e..be26f5ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8010,6 +8010,7 @@ dependencies = [ "hex", "jsonrpsee 0.24.10", "once_cell", + "openssl", "pprof", "proposer-reth-map", "rayon", diff --git a/bin/gravity_node/Cargo.toml b/bin/gravity_node/Cargo.toml index f28dc8d4..15b45b7c 100644 --- a/bin/gravity_node/Cargo.toml +++ b/bin/gravity_node/Cargo.toml @@ -46,6 +46,18 @@ gaptos = { workspace = true, features = ["gcp-secret-manager"] } block-buffer-manager.workspace = true proposer-reth-map.workspace = true build-info.workspace = true +# Force libssl to be statically linked into the binary so it can ship as a +# single self-contained artifact across libssl1.1 (Debian 11) and libssl3 +# (Debian 12+) hosts. native-tls (pulled transitively via aptos-vault-client +# and google-cloud-* under gaptos's gcp-secret-manager feature) is the +# consumer; with `vendored`, openssl-sys compiles OpenSSL from source and +# statically links it, removing the runtime libssl.so.* dependency. +# gravity_cli applies the same trick — keep them in sync. Without this, +# `cargo build -p gravity_node` (e.g. via `make gravity_node`) scopes the +# feature resolve to gravity_node only and openssl-sys defaults to dynamic +# system linkage, producing a binary that fails on hosts whose libssl ABI +# differs from the build host. +openssl = { version = "0.10", features = ["vendored"] } rayon = "1.7.0" tracing-flame = "0.2" tracing-timing = "0.6" diff --git a/docker/gravity_node/Dockerfile b/docker/gravity_node/Dockerfile index d49df49f..e930da0d 100644 --- a/docker/gravity_node/Dockerfile +++ b/docker/gravity_node/Dockerfile @@ -1,6 +1,25 @@ # syntax=docker/dockerfile:1.7 +# +# Two build targets: +# +# 1. `runtime` (default) — compile gravity_node from source inside the +# container's `builder` stage: +# docker build -t gravity_node:src \ +# -f docker/gravity_node/Dockerfile . +# +# 2. `runtime-host-binary` — skip the cargo build; copy a pre-built +# binary from the host build context. Used by the release pipeline so +# we don't recompile after the runner has already produced a native +# binary, and by regression/pfn_chain_stress for the same reason: +# docker build --target runtime-host-binary \ +# --build-arg HOST_BINARY=target/quick-release/gravity_node \ +# -t gravity_node:rel \ +# -f docker/gravity_node/Dockerfile . +# +# Stage pruning means `--target runtime-host-binary` does not enter the +# `builder` stage — the cargo build is genuinely skipped, not just cached. -# ─── Stage 1: builder ──────────────────────────────────────────────── +# ─── Stage 1: builder (used only by the `runtime` target) ──────────── FROM rust:1.93-slim-bookworm AS builder ARG CARGO_PROFILE=release @@ -12,13 +31,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libclang-dev \ build-essential \ pkg-config \ - libssl-dev \ libudev-dev \ libzstd-dev \ cmake \ git \ ca-certificates \ protobuf-compiler \ + perl \ && rm -rf /var/lib/apt/lists/* WORKDIR /build @@ -32,33 +51,52 @@ RUN --mount=type=cache,target=/build/target,id=gravity-target \ && mkdir -p /out \ && cp "target/${CARGO_PROFILE}/${CARGO_BIN}" /out/gravity_node -# ─── Stage 2: runtime ──────────────────────────────────────────────── -FROM debian:12-slim AS runtime +# ─── Stage 2: runtime-base (shared apt + user setup + entrypoint) ──── +# Common to both leaf targets. The binary itself is added in the leaves +# so we can pick the source (builder vs build context) per target. +FROM ubuntu:24.04 AS runtime-base RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ - libssl3 \ - libudev1 \ - libzstd1 \ jq \ tini \ - curl \ && rm -rf /var/lib/apt/lists/* \ && groupadd --system --gid 10001 gravity \ && useradd --system --uid 10001 --gid gravity --home-dir /gravity --shell /usr/sbin/nologin gravity \ && mkdir -p /gravity/config /gravity/data /gravity/logs \ && chown -R gravity:gravity /gravity -COPY --from=builder /out/gravity_node /usr/local/bin/gravity_node COPY docker/gravity_node/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/gravity_node /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh -USER gravity WORKDIR /gravity ENV GRAVITY_CONFIG_DIR=/gravity/config \ GRAVITY_DATA_DIR=/gravity/data \ RUST_BACKTRACE=1 +# Note: USER and ENTRYPOINT/CMD are set in each leaf so that the binary +# COPY can happen as root (default user in runtime-base) before dropping +# to `gravity`. + +# ─── Stage 3a: runtime-host-binary — copy a pre-built host binary ──── +# Used by the release pipeline and regression/pfn_chain_stress. +FROM runtime-base AS runtime-host-binary + +ARG HOST_BINARY=docker/gravity_node/bin/gravity_node +COPY --chmod=755 ${HOST_BINARY} /usr/local/bin/gravity_node + +USER gravity +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"] +CMD ["node"] + +# ─── Stage 3b: runtime (default target) — copy from in-container build ─ +# Put last so `docker build .` (no --target) picks this and the +# from-source build is the default behaviour, matching prior versions. +FROM runtime-base AS runtime + +COPY --from=builder --chmod=755 /out/gravity_node /usr/local/bin/gravity_node + +USER gravity ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"] CMD ["node"] diff --git a/docker/gravity_node/Dockerfile.host-binary b/docker/gravity_node/Dockerfile.host-binary deleted file mode 100644 index adca5dda..00000000 --- a/docker/gravity_node/Dockerfile.host-binary +++ /dev/null @@ -1,47 +0,0 @@ -# Runtime-only image; copies a pre-built gravity_node binary from the build -# context. Used as a fast fallback when the from-source build (Dockerfile) -# can't complete in-container — typically because the network inside the -# build sandbox can't reach github / crates.io reliably (SSL handshake -# failures on cargo git deps are the most common symptom). -# -# Usage: -# docker build -t gravity_node:pfn-stress \ -# -f docker/gravity_node/Dockerfile.host-binary \ -# --build-arg HOST_BINARY=target/quick-release/gravity_node \ -# . -# -# The build context must be the repo root and contain a pre-built binary at -# $HOST_BINARY. Build the host binary first: -# RUSTFLAGS="--cfg tokio_unstable" cargo build --bin gravity_node --profile quick-release - -FROM debian:12-slim AS runtime - -ARG HOST_BINARY=docker/gravity_node/bin/gravity_node - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - libssl3 \ - libudev1 \ - libzstd1 \ - jq \ - tini \ - curl \ - && rm -rf /var/lib/apt/lists/* \ - && groupadd --system --gid 10001 gravity \ - && useradd --system --uid 10001 --gid gravity --home-dir /gravity --shell /usr/sbin/nologin gravity \ - && mkdir -p /gravity/config /gravity/data /gravity/logs \ - && chown -R gravity:gravity /gravity - -COPY ${HOST_BINARY} /usr/local/bin/gravity_node -COPY docker/gravity_node/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/gravity_node /usr/local/bin/entrypoint.sh - -USER gravity -WORKDIR /gravity - -ENV GRAVITY_CONFIG_DIR=/gravity/config \ - GRAVITY_DATA_DIR=/gravity/data \ - RUST_BACKTRACE=1 - -ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"] -CMD ["node"] diff --git a/regression/README.md b/regression/README.md index 3cf58f23..ec254d3c 100644 --- a/regression/README.md +++ b/regression/README.md @@ -17,6 +17,6 @@ Drop a new subdirectory with at minimum: - `stop.sh` — tear down - `README.md` — topology, baselines, caveats -Reuse `docker/gravity_node/Dockerfile.host-binary` for the node image — -no good reason to in-container-compile for a regression test that needs -fast iteration. +Reuse `docker/gravity_node/Dockerfile` with `--target runtime-host-binary` +for the node image — no good reason to in-container-compile for a regression +test that needs fast iteration. diff --git a/regression/pfn_chain_stress/README.md b/regression/pfn_chain_stress/README.md index 231c58da..e0d9992e 100644 --- a/regression/pfn_chain_stress/README.md +++ b/regression/pfn_chain_stress/README.md @@ -94,9 +94,10 @@ cd regression/pfn_chain_stress 1. **Build on host, wrap in Docker.** `run.sh` first runs `cargo build --bin gravity_node --profile quick-release` on the host (incremental — only seconds when sources unchanged), then stages the - binary at `docker/gravity_node/bin/gravity_node`. The Docker image - (`docker/gravity_node/Dockerfile.host-binary`) `COPY`s that binary - into a slim debian runtime — image build is ~12s after first time. + binary at `docker/gravity_node/bin/gravity_node`. The Docker image is + built with `docker/gravity_node/Dockerfile` using `--target + runtime-host-binary`, which `COPY`s that binary into an ubuntu:24.04 + runtime — image build is ~12s after first time. In-container builds from source were tried and abandoned: ~65 min apt install + cargo git-fetch SSL errors. Not worth it for a stress diff --git a/regression/pfn_chain_stress/docker-compose.yaml b/regression/pfn_chain_stress/docker-compose.yaml index 4da053bc..c4f3042f 100644 --- a/regression/pfn_chain_stress/docker-compose.yaml +++ b/regression/pfn_chain_stress/docker-compose.yaml @@ -28,7 +28,8 @@ x-node-common: &node-common # runtime image. run.sh does `cargo build` on the host before invoking # this — no in-container Rust compile (in-container builds chronically # hit SSL/cargo-git issues; not worth the 30-60 min cost). - dockerfile: docker/gravity_node/Dockerfile.host-binary + dockerfile: docker/gravity_node/Dockerfile + target: runtime-host-binary restart: unless-stopped network_mode: host # cpuset pins all nodes of this cluster to a fixed core range. Empty diff --git a/regression/pfn_chain_stress/run.sh b/regression/pfn_chain_stress/run.sh index 3af64c42..e5ad32fe 100755 --- a/regression/pfn_chain_stress/run.sh +++ b/regression/pfn_chain_stress/run.sh @@ -204,7 +204,8 @@ if ! docker image inspect gravity_node:pfn-stress >/dev/null 2>&1; then echo "[run] building gravity_node:pfn-stress…" DOCKER_BUILDKIT=1 docker build \ -t gravity_node:pfn-stress \ - -f "$REPO_ROOT/docker/gravity_node/Dockerfile.host-binary" \ + -f "$REPO_ROOT/docker/gravity_node/Dockerfile" \ + --target runtime-host-binary \ "$REPO_ROOT" \ || { echo "[run] gravity_node image build failed"; exit 1; } fi