From 0261dca96b3f9e6977a3ae91221fbf748955a4a7 Mon Sep 17 00:00:00 2001 From: Gilles Filippini <pini@debian.org> Date: Mon, 17 Apr 2023 15:42:51 +0200 Subject: [PATCH] ci: use podman gitlab-runner for multiarch builds The build runs in two stages: 1. Cross compilation for target architecture on native arch image 2. Installation into target architecture image Currently it requires a third stage to workaround an issue in podman/buildah: https://github.com/containers/buildah/issues/4742 Configuring a gitlab-runner with podman: 1. Follow this documentation: https://docs.gitlab.com/runner/executors/docker.html#use-podman-to-run-docker-commands 2. Install the `crun` and `qemu-user-static` packages --- .dockerignore | 1 + .gitlab-ci.yml | 258 +++++++++++++++++----------------------------- docker/Dockerfile | 85 ++++++++++----- 3 files changed, 151 insertions(+), 193 deletions(-) diff --git a/.dockerignore b/.dockerignore index c602282b3..1c3094162 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ docker/Dockerfile docker-compose.yml arm-build/ **/target/ +build/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 950da1fb6..4077c4ea0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,6 @@ +# Runner tags: +# - podman: use 'podman' to build multiplatform images + stages: - schedule - labels @@ -40,7 +43,7 @@ check_labels: .env: image: paritytech/ci-linux:1.68.2-buster tags: - - dind + - podman fmt_and_clippy: extends: .env @@ -58,95 +61,63 @@ fmt_and_clippy: - cargo clippy -- -V - cargo clippy --all --tests -- -D warnings -build_debug: - extends: .env - rules: - - if: $CI_COMMIT_TAG - when: never - - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "master"' - changes: - - Cargo.lock - - when: never +.docker_build: stage: build script: - - cargo clean -p duniter - - cargo build --locked - - mkdir build - - mv target/debug/duniter build/duniter - artifacts: - paths: - - build/ - expire_in: 3 day - cache: - - key: - files: - - Cargo.lock - paths: - - target/debug - policy: push + - echo podman build --layers --tag "$IMAGE_NAME:$IMAGE_TAG" -f docker/Dockerfile $PODMAN_BUILD_OPTIONS . + - podman build --layers --tag "$IMAGE_NAME:$IMAGE_TAG" -f docker/Dockerfile $PODMAN_BUILD_OPTIONS . + tags: + - podman -build_debug_with_cache: - extends: .env +.docker_deploy: + stage: deploy + before_script: + - podman login -u "duniterteam" -p "$DUNITERTEAM_PASSWD" docker.io + tags: + - podman + +.docker_deploy_native: + extends: .docker_deploy + script: + - podman push "localhost/$IMAGE_NAME:$IMAGE_TAG" "docker://docker.io/$IMAGE_NAME:$IMAGE_TAG" + +.docker_deploy_multiplatform: + extends: .docker_deploy + script: + - podman manifest rm "$MANIFEST" 2>/dev/null || true + - podman build --layers --platform linux/amd64,linux/arm64 --manifest "$MANIFEST" -f docker/Dockerfile $PODMAN_BUILD_OPTIONS . + - podman manifest push --all "$MANIFEST" "docker://docker.io/$IMAGE_NAME:$IMAGE_TAG" + after_script: + - podman manifest rm "$MANIFEST" + variables: + MANIFEST: "localhost/manifest-$IMAGE_NAME:$IMAGE_TAG" + +build_debug: + extends: .docker_build rules: - - changes: - - Cargo.lock - when: never - if: $CI_COMMIT_TAG when: never - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "master"' - when: never - stage: build - script: - - cargo clean -p duniter - - cargo build --locked - - mkdir build - - mv target/debug/duniter build/duniter - artifacts: - paths: - - build/ - expire_in: 3 day - cache: - - key: - files: - - Cargo.lock - paths: - - target/debug - policy: pull + variables: + IMAGE_NAME: "duniter/duniter-v2s" + IMAGE_TAG: "debug-sha-$CI_COMMIT_SHORT_SHA" + PODMAN_BUILD_OPTIONS: "--build-arg debug=1" build_release: - extends: .env + extends: .docker_build rules: - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v*/" - - when: never - stage: build - script: - - cargo build --locked --release - - mkdir build - - mv target/release/duniter build/duniter - artifacts: - paths: - - build/ - expire_in: 3 day - -build_release_manual: - extends: .env - rules: - - if: $CI_COMMIT_TAG - when: never + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "master"' - when: manual - stage: build - allow_failure: true - script: - - cargo build --locked --release - - mkdir build - - mv target/release/duniter build/duniter - artifacts: - paths: - - build/ - expire_in: 3 day + variables: + IMAGE_NAME: "duniter/duniter-v2s" + IMAGE_TAG: "sha-$CI_COMMIT_SHORT_SHA" + PODMAN_BUILD_OPTIONS: "--platform linux/amd64" -tests_debug: - extends: .env +test_debug: + stage: tests + extends: .docker_build rules: - if: $CI_COMMIT_REF_NAME =~ /^wip*$/ when: manual @@ -154,128 +125,83 @@ tests_debug: when: never - if: '$CI_MERGE_REQUEST_ID || $CI_COMMIT_BRANCH == "master"' - when: manual - stage: tests variables: - DUNITER_BINARY_PATH: "../build/duniter" - DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT: "20" - script: - - cargo test --workspace --exclude duniter-end2end-tests --exclude duniter-live-tests - - cargo cucumber -i account_creation* - - cargo cucumber -i certification* - - cargo cucumber -i identity_creation* - - cargo cucumber -i monetary_mass* - - cargo cucumber -i oneshot_account* - - cargo cucumber -i transfer_all* - after_script: - - cd target/debug/deps/ - - rm cucumber_tests-*.d - - mv cucumber_tests* ../../../build/duniter-cucumber - artifacts: - paths: - - build/ - expire_in: 3 day + IMAGE_NAME: "duniter/duniter-v2s-test" + IMAGE_TAG: "debug-sha-$CI_COMMIT_SHORT_SHA" + PODMAN_BUILD_OPTIONS: "--target build --build-arg debug=1 --build-arg cucumber=1" -tests_release: - extends: .env +test_release: + stage: tests + extends: .docker_build rules: - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v*/" - when: never - stage: tests variables: - DUNITER_BINARY_PATH: "../build/duniter" - DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT: "20" - script: - - cargo test --workspace --exclude duniter-end2end-tests --exclude duniter-live-tests - - cargo cucumber -i account_creation* - - cargo cucumber -i certification* - - cargo cucumber -i identity_creation* - - cargo cucumber -i monetary_mass* - - cargo cucumber -i oneshot_account* - - cargo cucumber -i transfer_all* - after_script: - - cd target/debug/deps/ - - rm cucumber_tests-*.d - - mv cucumber_tests* ../../../build/duniter-cucumber - artifacts: - paths: - - build/ - expire_in: 3 day - dependencies: - - build_release + IMAGE_NAME: "duniter/duniter-v2s-test" + IMAGE_TAG: "sha-$CI_COMMIT_SHORT_SHA" + PODMAN_BUILD_OPTIONS: "--target build --build-arg cucumber=1" -.docker-build-app-image: +deploy_docker_debug_sha: stage: deploy - image: docker:18.06 - tags: - - docker - services: - - docker:dind - before_script: - - docker info - script: - - docker pull $CI_REGISTRY_IMAGE:$IMAGE_TAG || true - - docker build --cache-from $CI_REGISTRY_IMAGE:$IMAGE_TAG --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" -f $DOCKERFILE_PATH . - - docker login -u "duniterteam" -p "$DUNITERTEAM_PASSWD" - - docker tag "$CI_REGISTRY_IMAGE:$IMAGE_TAG" "duniter/duniter-v2s:$IMAGE_TAG" - - docker push "duniter/duniter-v2s:$IMAGE_TAG" - -deploy_docker_test_image: - extends: .docker-build-app-image + extends: .docker_deploy_native rules: - - if: $CI_COMMIT_REF_NAME =~ /^wip*$/ - when: manual - - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH == "master"' + - if: $CI_COMMIT_TAG when: never - - when: manual - allow_failure: true + - if: $CI_COMMIT_BRANCH == "master" variables: - DOCKERFILE_PATH: "docker/Dockerfile" - IMAGE_TAG: "test-image-$CI_COMMIT_SHORT_SHA" + IMAGE_NAME: "duniter/duniter-v2s" + IMAGE_TAG: "debug-sha-$CI_COMMIT_SHORT_SHA" -deploy_docker_debug_sha: - extends: .docker-build-app-image +deploy_docker_release_sha: + stage: deploy + extends: .docker_deploy_native rules: - if: $CI_COMMIT_TAG when: never - - if: $CI_COMMIT_BRANCH == "master" + - when: manual variables: - DOCKERFILE_PATH: "docker/Dockerfile" - IMAGE_TAG: "debug-sha-$CI_COMMIT_SHORT_SHA" - after_script: - - docker login -u "duniterteam" -p "$DUNITERTEAM_PASSWD" - - docker tag "duniter/duniter-v2s:$IMAGE_TAG" "duniter/duniter-v2s:debug-latest" - - docker push "duniter/duniter-v2s:debug-latest" + IMAGE_NAME: "duniter/duniter-v2s" + IMAGE_TAG: "sha-$CI_COMMIT_SHORT_SHA" -deploy_docker_release_sha: - extends: .docker-build-app-image +deploy_docker_release_sha_multiplatform: + stage: deploy + needs: ["deploy_docker_release_sha"] + extends: .docker_deploy_multiplatform rules: - if: $CI_COMMIT_TAG when: never - when: manual - allow_failure: true variables: - DOCKERFILE_PATH: "docker/Dockerfile" + IMAGE_NAME: "duniter/duniter-v2s" IMAGE_TAG: "sha-$CI_COMMIT_SHORT_SHA" - dependencies: - - build_release_manual deploy_docker_release_tag: - extends: .docker-build-app-image + stage: deploy + extends: .docker_deploy_native rules: - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v*/" - when: never variables: - DOCKERFILE_PATH: "docker/Dockerfile" + IMAGE_NAME: "duniter/duniter-v2s" + IMAGE_TAG: "$CI_COMMIT_TAG" + +deploy_docker_release_tag_multiplatform: + stage: deploy + needs: ["deploy_docker_release_tag"] + extends: .docker_deploy_multiplatform + rules: + - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v*/" + - when: never + script: + - !reference [.docker_deploy_multiplatform, script] + - podman manifest push --all "$MANIFEST" "docker://docker.io/$IMAGE_NAME:latest" + variables: + IMAGE_NAME: "duniter/duniter-v2s" IMAGE_TAG: "$CI_COMMIT_TAG" - after_script: - - docker login -u "duniterteam" -p "$DUNITERTEAM_PASSWD" - - docker tag "duniter/duniter-v2s:$IMAGE_TAG" "duniter/duniter-v2s:latest" - - docker push "duniter/duniter-v2s:latest" - dependencies: - - build_release readme_docker_release_tag: stage: deploy_readme + needs: ["deploy_docker_release_tag"] rules: - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v*/" - when: never @@ -283,7 +209,7 @@ readme_docker_release_tag: name: chko/docker-pushrm entrypoint: ["/bin/sh", "-c", "/docker-pushrm"] variables: - DOCKER_USER: "duniterteam" + DOCKER_USER: duniterteam DOCKER_PASS: "$DUNITERTEAM_PASSWD" PUSHRM_SHORT: "Duniter v2 based on Substrate framework" PUSHRM_TARGET: "docker.io/duniter/duniter-v2s" diff --git a/docker/Dockerfile b/docker/Dockerfile index 84b1b6af0..7b3e0dd7c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,44 +1,76 @@ +# Workaround for https://github.com/containers/buildah/issues/4742 +FROM debian:bullseye-slim as target + # ------------------------------------------------------------------------------ # Build Stage # ------------------------------------------------------------------------------ -# Building for Debian buster because we need the binary to be compatible -# with the image paritytech/ci-linux:production (currently based on -# debian:buster-slim) used by the gitlab CI -FROM rust:1-buster as build +# When building for a foreign arch, use cross-compilation +# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/ +FROM --platform=$BUILDPLATFORM rust:1-bullseye as build +ARG BUILDPLATFORM +ARG TARGETPLATFORM + +# We need the target arch triplet in both Debian and rust flavor +RUN echo "DEBIAN_ARCH_TRIPLET='$(dpkg-architecture -A${TARGETPLATFORM#linux/} -qDEB_TARGET_MULTIARCH)'" >>/root/dynenv +RUN . /root/dynenv && \ + echo "RUST_ARCH_TRIPLET='$(echo "$DEBIAN_ARCH_TRIPLET" | sed -E 's/-linux-/-unknown&/')'" >>/root/dynenv + WORKDIR /root # Copy source tree COPY . . -RUN test -x build/duniter || \ - ( \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y clang cmake protobuf-compiler \ - ) +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y clang cmake protobuf-compiler # build duniter -ARG threads=1 -RUN test -x build/duniter || \ - ( \ - cargo build --release -j $threads && \ - mkdir -p build && \ - mv target/release/duniter build/ \ - ) - -# Create fake duniter-cucumber if is not exist -# The goal is to avoid error later, this binary is optional -RUN test -x build/duniter-cucumber || \ - ( \ - mkdir -p build && \ - touch build/duniter-cucumber \ - ) +ARG debug=0 +RUN if [ "$debug" = 0 ]; then \ + echo "CARGO_OPTIONS=--release" >>/root/dynenv && \ + echo "TARGET_FOLDER=release" >>/root/dynenv; \ + else \ + echo "TARGET_FOLDER=debug" >>/root/dynenv; \ + fi + +# Configure cross-build environment if need be +RUN set -x && \ + if [ "$TARGETPLATFORM" != "$BUILDPLATFORM" ]; then \ + . /root/dynenv && \ + apt install -y gcc-$DEBIAN_ARCH_TRIPLET binutils-$DEBIAN_ARCH_TRIPLET && \ + rustup target add "$RUST_ARCH_TRIPLET" && \ + : https://github.com/rust-lang/cargo/issues/4133 && \ + echo "RUSTFLAGS='-C linker=$DEBIAN_ARCH_TRIPLET-gcc'; export RUSTFLAGS" >>/root/dynenv; \ + fi + +# Build +RUN set -x && \ + cat /root/dynenv && \ + . /root/dynenv && \ + cargo build --locked $CARGO_OPTIONS --target "$RUST_ARCH_TRIPLET" && \ + mkdir -p build && \ + mv target/$RUST_ARCH_TRIPLET/$TARGET_FOLDER/duniter build/ + +# Run tests if requested, expted when cross-building +ARG cucumber=0 +RUN if [ "$cucumber" != 0 ] && [ "$TARGETPLATFORM" = "$BUILDPLATFORM" ]; then \ + cargo test --workspace --exclude duniter-end2end-tests --exclude duniter-live-tests && \ + cargo cucumber -i account_creation* && \ + cargo cucumber -i certification* && \ + cargo cucumber -i identity_creation* && \ + cargo cucumber -i monetary_mass* && \ + cargo cucumber -i oneshot_account* && \ + cargo cucumber -i transfer_all* && \ + cd target/debug/deps/ && \ + rm cucumber_tests-*.d && \ + mv cucumber_tests* ../../../build/duniter-cucumber; \ + fi # ------------------------------------------------------------------------------ # Final Stage # ------------------------------------------------------------------------------ -FROM debian:buster-slim +FROM target LABEL maintainer="Gilles Filippini <gilles.filippini@pini.fr>" LABEL version="0.0.0" @@ -54,6 +86,5 @@ ENTRYPOINT ["docker-entrypoint"] USER duniter # Intall -COPY --from=build /root/build/duniter /usr/local/bin/duniter -COPY --from=build /root/build/duniter-cucumber /usr/local/bin/duniter-cucumber +COPY --from=build /root/build /usr/local/bin/ COPY docker/docker-entrypoint /usr/local/bin/ -- GitLab