From 6bfb42584bb0f5f34e7cd1752863a0084d5f607a Mon Sep 17 00:00:00 2001 From: Florian Widder Date: Fri, 22 Jan 2021 10:00:23 +0100 Subject: [PATCH] Add Multi Platform Build for Docker-Hub Credits to https://github.com/rmoriz/multiarch-test --- Dockerfile | 5 +++- README.md | 16 ----------- build-multi-platform.sh | 59 ----------------------------------------- hooks/.config | 54 +++++++++++++++++++++++++++++++++++++ hooks/build | 57 +++++++++++++++++++++++++++++++++++++++ hooks/get-qemu.sh | 15 +++++++++++ hooks/post_checkout | 48 +++++++++++++++++++++++++++++++++ hooks/pre_build | 10 +++++++ hooks/push | 53 ++++++++++++++++++++++++++++++++++++ 9 files changed, 241 insertions(+), 76 deletions(-) delete mode 100755 build-multi-platform.sh create mode 100644 hooks/.config create mode 100644 hooks/build create mode 100644 hooks/get-qemu.sh create mode 100644 hooks/post_checkout create mode 100644 hooks/pre_build create mode 100644 hooks/push diff --git a/Dockerfile b/Dockerfile index 2405d1b..86702df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,11 @@ COPY ui ./ RUN npm install && \ node_modules/.bin/ng build --prod +ARG BASE_IMAGE_PREFIX +FROM ${BASE_IMAGE_PREFIX}python:3.8-alpine -FROM python:3.8-alpine +ARG ARCH +COPY qemu-${ARCH}-static* /usr/bin WORKDIR /app diff --git a/README.md b/README.md index 2c900ff..c1e52b7 100644 --- a/README.md +++ b/README.md @@ -70,22 +70,6 @@ A Docker image can be built locally (it will build the UI too): docker build -t metube . ``` -A Multi-Platform Docker image can be built locally: - -```bash -# Build with tag "metube" -build-multi-platform.sh - -# Build with tag "other_tag" -build-multi-platform.sh -t "other_tag" - -# Build with tag "metube" and Upload do Docker Registry -build-multi-platform.sh -p - -# Build with tag "other_tag" and Upload do Docker Registry -build-multi-platform.sh -t "other_tag" -p -``` - ## Development notes * The above works on Windows as well as Linux. diff --git a/build-multi-platform.sh b/build-multi-platform.sh deleted file mode 100755 index b1f78ad..0000000 --- a/build-multi-platform.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -TAG="metube" -PUSH="false" - -# Proccess Arguments - -while getopts ":hhelpf:t:p:" ARGS; -do - case $ARGS in - h|help ) - echo -e "Usgae:\n\t-t \"Tag\"\n\t\tuse a specific Tag\n\t-p\n\t\tPush the Image do a registry. Needs to be logged in (docker login).\n\t-h\n\t\t Show this Help Dialog" - exit 1 - ;; - t ) - TAG="${OPTARG}" - ;; - p ) - PUSH="true" - ;; - esac -done - -# Fail on Error -set -e - -# Register Arm executables to run on x64 machines -docker run --rm --privileged docker/binfmt:820fdd95a9972a5308930a2bdfb8573dd4447ad3 - -# Test if qemu is enabeld -cat /proc/sys/fs/binfmt_misc/qemu-aarch64 | head -n 1 | grep -q "enabled" - -# Create a Builder if needed -if docker buildx ls | grep -q "multiArchBuilder"; then - echo "Builder \"multiArchBuilder\" aready exists." -else - echo "Builder \"multiArchBuilder\" will be created." - - docker buildx create --name "multiArchBuilder" -fi - -# Switch to Builder "multiArchBuilder" -docker buildx use "multiArchBuilder" - -# Test if current Builder Contains needed Architectures -BOOTSTRAP=$(docker buildx inspect --bootstrap) - -echo $BOOTSTRAP | grep -q "linux/amd64" -echo $BOOTSTRAP | grep -q "linux/arm64" -echo $BOOTSTRAP | grep -q "linux/arm/v7" - -# Build Image -echo "Use Tag \"$TAG\" for Build" -if [ "$PUSH" = "true" ]; then - # Build with Push - docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t "$TAG" --push . -else - # Build without Push - docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t "$TAG" . -fi diff --git a/hooks/.config b/hooks/.config new file mode 100644 index 0000000..0bdaa18 --- /dev/null +++ b/hooks/.config @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set +u +echo "variables (see https://docs.docker.com/docker-hub/builds/advanced/):" +echo "SOURCE_BRANCH: $SOURCE_BRANCH" +echo "SOURCE_COMMIT: $SOURCE_COMMIT" +echo "COMMIT_MSG: $COMMIT_MSG" +echo "DOCKER_REPO: $DOCKER_REPO" +echo "DOCKERFILE_PATH: $DOCKERFILE_PATH" +echo "CACHE_TAG: $CACHE_TAG" +echo "IMAGE_NAME: $IMAGE_NAME" +echo + +: "${DOCKERFILE_PATH:=./Dockerfile}" +: "${IMAGE_NAME:=rmoriz/openra}" + +echo "variables after applying defaults:" +echo "DOCKERFILE_PATH: $DOCKERFILE_PATH" +echo "IMAGE_NAME: $IMAGE_NAME" +echo + +export PATH="$PWD/docker:$PATH" + +# => +# https://hub.docker.com/u/arm64v8/ +# https://hub.docker.com/u/arm32v7/ +# https://hub.docker.com/u/arm32v6/ +# https://hub.docker.com/u/arm32v5/ +declare -A base_image_prefix_map=( ["aarch64"]="arm64v8/" ["arm"]="arm32v5/" ["amd64"]="") + +# => dpkg -L qemu-user-static | grep /usr/bin/ +declare -A docker_qemu_arch_map=( ["aarch64"]="aarch64" ["arm"]="arm" ["amd64"]="x86_64") + +# => https://github.com/docker/docker-ce/blob/76ac3a4952a9c03f04f26fc88d3160acd51d1702/components/cli/cli/command/manifest/util.go#L22 +declare -A docker_to_manifest_map=( ["aarch64"]="arm64" ["arm"]="arm" ["amd64"]="amd64") + +# what we want to build +build_architectures=(amd64) +verified_build_architectures=() +verified_build_architectures+=("$(docker version -f '{{.Server.Arch}}')") + +# what we can build +for arch in ${build_architectures[@]}; do + if [ -f "qemu-${docker_qemu_arch_map[${arch}]}-static" ]; then + echo "qemu binary for $arch found"; + verified_build_architectures+=($arch) + fi +done + +echo $verified_build_architectures +set -u + +docker -v +echo \ No newline at end of file diff --git a/hooks/build b/hooks/build new file mode 100644 index 0000000..7f91680 --- /dev/null +++ b/hooks/build @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -eu + +echo "🔵 build" +source hooks/.config + +echo "✅ Will build the following architectures: $verified_build_architectures" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + +for arch in ${verified_build_architectures[@]}; do + echo "✅ building $arch" + echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + + BASE_IMAGE_PREFIX="${base_image_prefix_map[${arch}]}" + docker build \ + --build-arg BASE_IMAGE_PREFIX=${BASE_IMAGE_PREFIX} \ + --build-arg ARCH=${arch} \ + --file $DOCKERFILE_PATH \ + --tag "${IMAGE_NAME}-${arch}" \ + . +done + +echo "✅ images built:" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +docker image ls + +# https://github.com/moby/moby/issues/36552 +# +tempdir=$(mktemp -d -t yolo.XXXXXXXX) +cd $tempdir + +for arch in ${verified_build_architectures[@]}; do + echo "✅ yolo fixing platform $arch" + echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + + manifest_arch=${docker_to_manifest_map[${arch}]} + docker save "${IMAGE_NAME}-${arch}"| tar xv + + for filename in */json; do + [ -e "$filename" ] || continue + jq --compact-output 'del(.architecture)' < "$filename" | sponge "$filename" + done + + for filename in *.json; do + [ -e "$filename" ] || continue + ! [ $filename = "manifest.json" ] || continue + + jq --arg architecture "$manifest_arch" \ + --compact-output '.architecture=$architecture' < "$filename" | sponge "$filename" + done + + tar cv . | docker load + rm -rf $tempdir/* +done + +trap "exit 1" HUP INT PIPE QUIT TERM +trap "rm -rf $tempdir" EXIT \ No newline at end of file diff --git a/hooks/get-qemu.sh b/hooks/get-qemu.sh new file mode 100644 index 0000000..d557237 --- /dev/null +++ b/hooks/get-qemu.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -ex + +# NOTE: this url will change regularly because it's unstable +PACKAGE=http://ftp.de.debian.org/debian/pool/main/q/qemu/qemu-user-static_4.2-2_amd64.deb + +mkdir tmp/ +cd tmp/ + +curl $PACKAGE -o $(basename ${PACKAGE}) +dpkg-deb -X $(basename ${PACKAGE}) . +cp usr/bin/qemu-aarch64-static .. +cp usr/bin/qemu-arm-static .. +cd .. +rm -rf tmp \ No newline at end of file diff --git a/hooks/post_checkout b/hooks/post_checkout new file mode 100644 index 0000000..cacdcc2 --- /dev/null +++ b/hooks/post_checkout @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -eu + +echo "🔵 post_checkout" +source hooks/.config + +echo "✅ Install qemu + binfmt support" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +# it's an Ubuntu VM and you can install stuff. +apt-get update +apt-get install -y curl qemu-user-static binfmt-support jq moreutils + +# Sadly docker itself uses Docker EE 17.06 on Dockerhub which does not support +# manifests. +echo "✅ Install a fresh docker cli binary" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + +curl https://download.docker.com/linux/static/stable/x86_64/docker-18.09.1.tgz | \ + tar xvz docker/docker + +echo "✅ Build a usable config.json file" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +# Manifests are still experimental and enabled by a config file flag. +# Interestingly, there is no config file and the credential parts to push +# images is available in an environment variable. Let's create a config file to +# combine the two things: +# +mkdir -p ~/.docker +jq --null-input --argjson auths "$DOCKERCFG" '. + {auths: $auths}' | \ +jq --arg experimental enabled '. + {experimental: $experimental}' | \ +sponge ~/.docker/config.json + +echo "✅ Copy qemu binaries into docker build context" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +# The current setup copies the qemu binary into the image (see Dockerfile) +# Pro: +# - it's easy to run non-amd64 images on amd64 systems for debugging +# Contra: +# - it's dead weight in the "destination" architecture and consumes space +# Alternative: +# - use a multistage Dockerfile (no RUN in the last stage possible of course) +# - wait for https://github.com/moby/moby/issues/14080 +# +for arch in ${build_architectures[@]}; do + cp /usr/bin/qemu-${docker_qemu_arch_map[${arch}]}-static qemu-${arch}-static +done + +ls -la \ No newline at end of file diff --git a/hooks/pre_build b/hooks/pre_build new file mode 100644 index 0000000..938f9f3 --- /dev/null +++ b/hooks/pre_build @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eu + +echo "🔵 pre_build" +source hooks/.config + +echo "✅ Register qemu-*-static for all supported processors except current" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + +docker run --rm --privileged multiarch/qemu-user-static:register --reset \ No newline at end of file diff --git a/hooks/push b/hooks/push new file mode 100644 index 0000000..6e6657f --- /dev/null +++ b/hooks/push @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -eu + +echo "🔵 push" +source hooks/.config + +# 1. push all images +IMAGE_NAME="${IMAGE_NAME//index.docker.io\/}" + +for arch in ${verified_build_architectures[@]}; do + echo "✅ Pushing ${IMAGE_NAME}-${arch}" + echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + echo + docker push ${IMAGE_NAME}-${arch} +done + +docker image ls + +# 2. build and push manifest +#DOCKER_REPO="index.docker.io/${DOCKER_REPO}" +manifests="" + +for arch in ${verified_build_architectures[@]}; do + manifests="${manifests} ${IMAGE_NAME}-${arch}" +done + +echo "✅ Creating manifest ${IMAGE_NAME}" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +docker manifest create ${IMAGE_NAME} \ + $manifests +echo + +echo "✅ Annotating manifest" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +for arch in ${verified_build_architectures[@]}; do + docker manifest annotate ${IMAGE_NAME} \ + ${IMAGE_NAME}-${arch} \ + --os linux \ + --arch ${docker_to_manifest_map[${arch}]} +done + +echo "✅ Inspecting manifest ${IMAGE_NAME}-${arch}" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +docker manifest inspect ${IMAGE_NAME}-${arch} +echo + +echo "✅ Pushing manifest ${IMAGE_NAME}" +echo "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" +docker manifest push --purge "${IMAGE_NAME}" +echo + +echo +echo "😊" \ No newline at end of file