Skip to content

Commit

Permalink
Build with buildkit (#9628)
Browse files Browse the repository at this point in the history
* generate multiarch images for non-architecture tags

* Update documentation related to multiarch Docker

* Remove qemu and switch to build via buildkit

* Move to multistage Dockerfile

* refactor docker script arg parsing and fix merge bugs

* removed unnecessary testing script and fixed function name

* improved quoting in shell scripts

---------

Co-authored-by: humanoid2050 <humanoid2050@monolith>
Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
Co-authored-by: humanoid2050 <humanoid2050@katana>
Co-authored-by: Brad Warren <bmw@eff.org>
  • Loading branch information
5 people committed Apr 8, 2023
1 parent 7a68b29 commit 9ee1eee
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 262 deletions.
22 changes: 13 additions & 9 deletions .azure-pipelines/templates/jobs/packaging-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ jobs:
vmImage: ubuntu-22.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
# The default timeout of 60 minutes is a little low for compiling
# cryptography on ARM architectures.
timeoutInMinutes: 180
Expand All @@ -32,24 +32,28 @@ jobs:
path: $(Build.ArtifactStagingDirectory)
artifact: docker_$(DOCKER_ARCH)
displayName: Store Docker artifact
- job: docker_run
- job: docker_test
dependsOn: docker_build
pool:
vmImage: ubuntu-22.04
strategy:
matrix:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: docker_amd64
artifact: docker_$(DOCKER_ARCH)
path: $(Build.SourcesDirectory)
displayName: Retrieve Docker images
- bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar
displayName: Load Docker images
- bash: |
set -ex
DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}:{{.Tag}}')
for DOCKER_IMAGE in ${DOCKER_IMAGES}
do docker run --rm "${DOCKER_IMAGE}" plugins --prepare
done
set -e && tools/docker/test.sh $(dockerTag) $DOCKER_ARCH
displayName: Run integration tests for Docker images
- job: installer_build
pool:
Expand Down
8 changes: 4 additions & 4 deletions .azure-pipelines/templates/stages/deploy-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ stages:
vmImage: ubuntu-22.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
steps:
- task: DownloadPipelineArtifact@2
inputs:
Expand All @@ -51,7 +51,7 @@ stages:
command: login
containerRegistry: docker-hub
displayName: Login to Docker Hub
- bash: set -e && tools/docker/deploy_by_arch.sh $(dockerTag) $DOCKER_ARCH
- bash: set -e && tools/docker/deploy_images.sh $(dockerTag) $DOCKER_ARCH
displayName: Deploy the Docker images by architecture
- job: publish_docker_multiarch
dependsOn: publish_docker_by_arch
Expand All @@ -63,5 +63,5 @@ stages:
command: login
containerRegistry: docker-hub
displayName: Login to Docker Hub
- bash: set -e && tools/docker/deploy_multiarch.sh $(dockerTag)
- bash: set -e && tools/docker/deploy_manifests.sh $(dockerTag) all
displayName: Deploy the Docker multiarch manifests
17 changes: 9 additions & 8 deletions tools/docker/core/Dockerfile → tools/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
# Docker Arch (amd64, arm32v6, ...)
ARG TARGET_ARCH
FROM ${TARGET_ARCH}/python:3.10-alpine3.16

# Qemu Arch (x86_64, arm, ...)
ARG QEMU_ARCH
ENV QEMU_ARCH=${QEMU_ARCH}
COPY qemu-${QEMU_ARCH}-static /usr/bin/
#base image
FROM python:3.10-alpine3.16 as certbot

ENTRYPOINT [ "certbot" ]
EXPOSE 80 443
Expand Down Expand Up @@ -45,3 +39,10 @@ RUN apk add --no-cache --virtual .build-deps \
--editable src/certbot \
&& apk del .build-deps \
&& rm -rf ${HOME}/.cargo

#static definition for making a plugin, but beware that
#using this layer definition will cause collisions if you make
#extensive use of the cache.
FROM certbot as certbot-plugin
COPY --from=plugin-src . /opt/certbot/src/plugin
RUN python tools/pip_install.py --no-cache-dir --editable /opt/certbot/src/plugin
33 changes: 20 additions & 13 deletions tools/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,29 @@ High-level behavior
-------------------

Running `./build.sh <TAG> all` causes the Docker images to be built for all
supported architectures, where `<TAG>` is the base of the tag that should be
given to the generated images. The tag should either be `nightly` or a git
version tag like `v2.2.0`. The given tag is only the base of the tag because
the CPU architecture is also added to the tag. For version tags above `v2.0.0`,
Additional tags for `latest` are also generated. The generated images are stored
in the local docker image cache.

Running `./deploy_by_arch.sh <TAG> all && ./deploy_multiarch.sh <TAG>` will
push the previously generated images to Docker Hub and then generate multi-arch
manifests for easy access to the underlying images appropriate for a given
architecture.
supported architectures. The generated images are stored in the local docker image cache.

Running `./test.sh <TAG> all` loads images from the docker image cache
and runs a test command to validate the image contents.

Running `./deploy_images.sh <TAG> all` will push the previously generated images
to Docker Hub. The <TAG> argument is an identifier applied to all docker
images and manifests. It may be something like `nightly` or `v2.3.2`. If
the tag is a version stamp greater than `v2.0.0`, then a `latest` tag will
also be generated and pushed to the docker hub repo.

Running `./deploy_manifests.sh <TAG> all` will add multiarch manifests to
Docker Hub. This command assumes that `./deploy_images.sh <TAG> all` has
been previously run with the same tag.

Configuration
-------------

To run these scripts you need:

1. An x86_64 machine with Docker installed and the Docker daemon running. You probably don't want to use the docker snap as these scripts have failed when using that in the past.
2. To be logged into Docker Hub with an account able to push to the Certbot and Certbot DNS Docker images on Docker Hub. Altering the value of `DOCKER_HUB_ORG` in `lib/common` will allow you to push to your own account for testing.
1. A computer with Docker installed and the Docker daemon running. You probably
don't want to use the docker snap as these scripts have failed when using that
in the past.
2. To be logged into Docker Hub with an account able to push to the Certbot and
Certbot DNS Docker images on Docker Hub. Altering the value of `DOCKER_HUB_ORG`
in `lib/common` will allow you to push to your own account for testing.
140 changes: 44 additions & 96 deletions tools/docker/build.sh
Original file line number Diff line number Diff line change
@@ -1,108 +1,56 @@
#!/bin/bash
set -euxo pipefail
IFS=$'\n\t'

# This script builds certbot docker and certbot dns plugins docker using the
# local Certbot files.
# This script builds docker images for certbot and each dns plugin from the
# local Certbot source files. Results are stored in the docker image cache

# Usage: ./build.sh [TAG] [all|amd64|arm32v6|arm64v8]
# with the [TAG] value corresponding the base of the tag to give the Docker
# images and the 2nd value being the architecture to build snaps for.
# Values for the tag should be something like `v0.34.0` or `nightly`. The
# given value is only the base of the tag because the things like the CPU
# architecture are also added to the full tag.
# Usage:
# ./build.sh <tag> all
# ./build.sh <tag> <architectures>
# The <tag> argument is used to identify the code version (e.g v2.3.1) or type of build
# (e.g. nightly). This will be used when saving images to the docker image cache.
# The argument "all" will build all known architectures. Alternatively, the
# user may provide a comma separated list of architectures drawn from the
# known architectures. Known architectures include amd64, arm32v6, and arm64v8.

WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
REPO_ROOT="$(dirname "$(dirname "${WORK_DIR}")")"
source "$WORK_DIR/lib/common"
source "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/lib/common"

trap Cleanup EXIT
ParseArgs "$@"

#jump to root, matching popd handed by Cleanup on EXIT via trap
pushd "${REPO_ROOT}"

Cleanup() {
rm -rf "$REPO_ROOT"/qemu-*-static || true
# Set trap here, as the popd won't work as expected if invoked prior to pushd
trap Cleanup EXIT
# Create the builder
CreateBuilder
InstallMultiarchSupport


BuildAndLoadByArch() {
TAG_ARCH=$1
docker buildx build --target certbot --builder certbot_builder \
--platform "$(arch2platform "$TAG_ARCH")" \
-f "${WORK_DIR}/Dockerfile" \
-t "${DOCKER_HUB_ORG}/certbot:${TAG_ARCH}-${TAG_VER}" \
--load \
.
for plugin in "${CERTBOT_PLUGINS[@]}"; do
rm -rf "$REPO_ROOT/certbot-$plugin"/qemu-*-static || true
docker buildx build --target certbot-plugin --builder certbot_builder \
--platform "$(arch2platform "$TAG_ARCH")" \
--build-context plugin-src="${REPO_ROOT}/certbot-${plugin}" \
-f "${WORK_DIR}/Dockerfile" \
-t "${DOCKER_HUB_ORG}/${plugin}:${TAG_ARCH}-${TAG_VER}" \
--load \
.
done
}

# Returns the translation from Docker to QEMU architecture
# Usage: GetQemuArch [amd64|arm32v6|arm64v8]
GetQemuArch() {
ARCH=$1

case "$ARCH" in
"amd64")
echo "x86_64"
;;
"arm32v6")
echo "arm"
;;
"arm64v8")
echo "aarch64"
;;
"*")
echo "Not supported build architecture '$1'." >&2
exit 1
esac
}

# Downloads QEMU static binary file for architecture
# Usage: DownloadQemuStatic [x86_64|arm|aarch64]
DownloadQemuStatic() {
ARCH=$1
# In principle, there is a better way to do with by using `docker buildx bake`
# instead of a for-loop. However, issues have been found in the results
# of such a build. See the branch buildx-bake and
# https://github.com/certbot/certbot/issues/9587.

QEMU_ARCH=$(GetQemuArch "$ARCH")
if [ ! -f "qemu-${QEMU_ARCH}-static" ]; then
QEMU_DOWNLOAD_URL="https://github.com/multiarch/qemu-user-static/releases/download"
QEMU_LATEST_TAG=$(curl -s https://api.github.com/repos/multiarch/qemu-user-static/tags \
| grep 'name.*v[0-9]' \
| head -n 1 \
| cut -d '"' -f 4)
curl -SL "${QEMU_DOWNLOAD_URL}/${QEMU_LATEST_TAG}/x86_64_qemu-$QEMU_ARCH-static.tar.gz" \
| tar xzv
fi
}

TAG_BASE="$1"
if [ -z "$TAG_BASE" ]; then
echo "We cannot tag Docker images with an empty string!" >&2
exit 1
fi
ParseRequestedArch "${2}"

# Register QEMU handlers
docker run --rm --privileged multiarch/qemu-user-static:register --reset

# Step 1: Certbot core Docker
DOCKER_REPO="${DOCKER_HUB_ORG}/certbot"
for TARGET_ARCH in "${ALL_REQUESTED_ARCH[@]}"; do
pushd "${REPO_ROOT}"
DownloadQemuStatic "${TARGET_ARCH}"
QEMU_ARCH=$(GetQemuArch "${TARGET_ARCH}")
DOCKER_BUILDKIT=0 docker build \
--build-arg TARGET_ARCH="${TARGET_ARCH}" \
--build-arg QEMU_ARCH="${QEMU_ARCH}" \
-f "${WORK_DIR}/core/Dockerfile" \
-t "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" \
.
popd
done

# Step 2: Certbot DNS plugins Docker images
for plugin in "${CERTBOT_PLUGINS[@]}"; do
DOCKER_REPO="${DOCKER_HUB_ORG}/${plugin}"
pushd "${REPO_ROOT}/certbot-${plugin}"
# Copy QEMU static binaries downloaded when building the core Certbot image
cp ../qemu-*-static .
for TARGET_ARCH in "${ALL_REQUESTED_ARCH[@]}"; do
QEMU_ARCH=$(GetQemuArch "${TARGET_ARCH}")
BASE_IMAGE="${DOCKER_HUB_ORG}/certbot:${TARGET_ARCH}-${TAG_BASE}"
DOCKER_BUILDKIT=0 docker build \
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
--build-arg QEMU_ARCH="${QEMU_ARCH}" \
-f "${WORK_DIR}/plugin/Dockerfile" \
-t "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" \
.
done
popd
done
for ARCH in "${REQUESTED_ARCH_ARRAY[@]}"; do
BuildAndLoadByArch "$ARCH"
done
64 changes: 0 additions & 64 deletions tools/docker/deploy_by_arch.sh

This file was deleted.

0 comments on commit 9ee1eee

Please sign in to comment.