diff --git a/.github/workflows/test-docker-container.yml b/.github/workflows/docker-ci.yaml similarity index 76% rename from .github/workflows/test-docker-container.yml rename to .github/workflows/docker-ci.yaml index 3e416f72c7..a95f0da1f1 100644 --- a/.github/workflows/test-docker-container.yml +++ b/.github/workflows/docker-ci.yaml @@ -1,4 +1,4 @@ -name: Test docker image and container +name: Docker CI on: push: @@ -7,7 +7,7 @@ on: branches: [ "master" ] jobs: - test-docker-image: + test-build-and-container: runs-on: ubuntu-latest strategy: fail-fast: false @@ -16,7 +16,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Test Image Build + - name: Test build_image.sh script with custom tagging and gpu flag + working-directory: docker + run: ./test_build_image_tagging.sh ${{ matrix.python-version }} + + - name: Build Image for container test id: image_build working-directory: docker run: | @@ -28,7 +32,7 @@ jobs: working-directory: docker run: ./test_container_health.sh ${{ steps.image_build.outputs.IMAGE_TAG }} - - name: Check Python Version in Container + - name: Check Python version in container working-directory: docker run: ./test_container_python_version.sh ${{ steps.image_build.outputs.IMAGE_TAG }} ${{ matrix.python-version }} diff --git a/docker/build_image.sh b/docker/build_image.sh index 246f81bdac..eeb123f2b9 100755 --- a/docker/build_image.sh +++ b/docker/build_image.sh @@ -1,14 +1,14 @@ #!/bin/bash +set -o errexit -o nounset -o pipefail + MACHINE=cpu BRANCH_NAME="master" DOCKER_TAG="pytorch/torchserve:latest-cpu" BUILD_TYPE="production" -DOCKER_FILE="Dockerfile" BASE_IMAGE="ubuntu:20.04" -CUSTOM_TAG=false +USE_CUSTOM_TAG=false CUDA_VERSION="" -UBUNTU_VERSION="ubuntu:20.04" USE_LOCAL_SERVE_FOLDER=false BUILD_WITH_IPEX=false PYTHON_VERSION=3.9 @@ -53,8 +53,8 @@ do shift ;; -t|--tag) - DOCKER_TAG="$2" - CUSTOM_TAG=true + CUSTOM_TAG="$2" + USE_CUSTOM_TAG=true shift shift ;; @@ -80,28 +80,28 @@ do # With default ubuntu version 20.04 -cv|--cudaversion) CUDA_VERSION="$2" - if [ $CUDA_VERSION == "cu118" ]; + if [ "${CUDA_VERSION}" == "cu118" ]; then BASE_IMAGE="nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu20.04" - elif [ $CUDA_VERSION == "cu117" ]; + elif [ "${CUDA_VERSION}" == "cu117" ]; then BASE_IMAGE="nvidia/cuda:11.7.0-cudnn8-runtime-ubuntu20.04" - elif [ $CUDA_VERSION == "cu116" ]; + elif [ "${CUDA_VERSION}" == "cu116" ]; then BASE_IMAGE="nvidia/cuda:11.6.0-cudnn8-runtime-ubuntu20.04" - elif [ $CUDA_VERSION == "cu113" ]; + elif [ "${CUDA_VERSION}" == "cu113" ]; then BASE_IMAGE="nvidia/cuda:11.3.0-cudnn8-runtime-ubuntu20.04" - elif [ $CUDA_VERSION == "cu111" ]; + elif [ "${CUDA_VERSION}" == "cu111" ]; then BASE_IMAGE="nvidia/cuda:11.1.1-cudnn8-runtime-ubuntu20.04" - elif [ $CUDA_VERSION == "cu102" ]; + elif [ "${CUDA_VERSION}" == "cu102" ]; then BASE_IMAGE="nvidia/cuda:10.2-cudnn8-runtime-ubuntu18.04" - elif [ $CUDA_VERSION == "cu101" ] + elif [ "${CUDA_VERSION}" == "cu101" ] then BASE_IMAGE="nvidia/cuda:10.1-cudnn7-runtime-ubuntu18.04" - elif [ $CUDA_VERSION == "cu92" ]; + elif [ "${CUDA_VERSION}" == "cu92" ]; then BASE_IMAGE="nvidia/cuda:9.2-cudnn7-runtime-ubuntu18.04" else @@ -120,22 +120,27 @@ then exit 1 fi -if [ "${BUILD_TYPE}" == "dev" ] && ! $CUSTOM_TAG ; +if [ "${BUILD_TYPE}" == "dev" ] && ! $USE_CUSTOM_TAG ; then DOCKER_TAG="pytorch/torchserve:dev-$MACHINE" fi -if [ "${BUILD_TYPE}" == "codebuild" ] && ! $CUSTOM_TAG ; +if [ "${BUILD_TYPE}" == "codebuild" ] && ! $USE_CUSTOM_TAG ; then DOCKER_TAG="pytorch/torchserve:codebuild-$MACHINE" fi -if [ $BUILD_TYPE == "production" ] +if [ "$USE_CUSTOM_TAG" = true ] +then + DOCKER_TAG=${CUSTOM_TAG} +fi + +if [ "${BUILD_TYPE}" == "production" ] then - DOCKER_BUILDKIT=1 docker build --file Dockerfile --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg CUDA_VERSION=$CUDA_VERSION --build-arg PYTHON_VERSION=$PYTHON_VERSION -t $DOCKER_TAG . -elif [ $BUILD_TYPE == "benchmark" ] + DOCKER_BUILDKIT=1 docker build --file Dockerfile --build-arg BASE_IMAGE="${BASE_IMAGE}" --build-arg CUDA_VERSION="${CUDA_VERSION}" --build-arg PYTHON_VERSION="${PYTHON_VERSION}" -t "${DOCKER_TAG}" . +elif [ "${BUILD_TYPE}" == "benchmark" ] then - DOCKER_BUILDKIT=1 docker build --pull --no-cache --file Dockerfile.benchmark --build-arg USE_LOCAL_SERVE_FOLDER=$USE_LOCAL_SERVE_FOLDER --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BRANCH_NAME=$BRANCH_NAME --build-arg CUDA_VERSION=$CUDA_VERSION --build-arg MACHINE_TYPE=$MACHINE --build-arg PYTHON_VERSION=$PYTHON_VERSION -t $DOCKER_TAG . + DOCKER_BUILDKIT=1 docker build --pull --no-cache --file Dockerfile.benchmark --build-arg USE_LOCAL_SERVE_FOLDER=$USE_LOCAL_SERVE_FOLDER --build-arg BASE_IMAGE="${BASE_IMAGE}" --build-arg BRANCH_NAME="${BRANCH_NAME}" --build-arg CUDA_VERSION="${CUDA_VERSION}" --build-arg MACHINE_TYPE="${MACHINE}" --build-arg PYTHON_VERSION="${PYTHON_VERSION}" -t "${DOCKER_TAG}" . else - DOCKER_BUILDKIT=1 docker build --pull --no-cache --file Dockerfile.dev -t $DOCKER_TAG --build-arg BUILD_TYPE=$BUILD_TYPE --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BRANCH_NAME=$BRANCH_NAME --build-arg CUDA_VERSION=$CUDA_VERSION --build-arg MACHINE_TYPE=$MACHINE --build-arg BUILD_WITH_IPEX=$BUILD_WITH_IPEX --build-arg PYTHON_VERSION=$PYTHON_VERSION . + DOCKER_BUILDKIT=1 docker build --pull --no-cache --file Dockerfile.dev -t "${DOCKER_TAG}" --build-arg BUILD_TYPE="${BUILD_TYPE}" --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BRANCH_NAME="${BRANCH_NAME}" --build-arg CUDA_VERSION="${CUDA_VERSION}" --build-arg MACHINE_TYPE="${MACHINE}" --build-arg BUILD_WITH_IPEX="${BUILD_WITH_IPEX}" --build-arg PYTHON_VERSION="${PYTHON_VERSION}" . fi diff --git a/docker/test_build_image_tagging.sh b/docker/test_build_image_tagging.sh new file mode 100755 index 0000000000..0bd9644d32 --- /dev/null +++ b/docker/test_build_image_tagging.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +# This test checks the parsing and handling of arguments in `build_image.sh`, +# making sure that `build_image.sh` is invariant to the order of the passed +# arguments `-py` (python version), `-t` (image tag) and `-g` (use gpu flag) +# and that tagging works properly. +# That means, we have 3 args, so there are 6 possibilities to order them and +# we expect these script runs to produce the *very same output*: +# +# $ ./build_image.sh -py "${VERSION}" -t "${TAG}" -g +# $ ./build_image.sh -py "${VERSION}" -g -t "${TAG}" +# $ ./build_image.sh -t "${TAG}" -py "${VERSION}" -g +# $ ./build_image.sh -t "${TAG}" -g -py "${VERSION}" +# $ ./build_image.sh -g -py "${VERSION}" -t "${TAG}" +# $ ./build_image.sh -g -t "${TAG}" -py "${VERSION}" +# +# In order to assert the equivalence of all these variations, we take advantage +# of how docker builds images: If two images are exactly the same (ie, they are +# composed of the very same layers) they will have the same digest (ie, a hash +# value representing the content of the image), regardless of the tag assigned +# to the image. So, for example, if we run (with the same Dockerfile): +# +# $ docker build -f Dockerfile -t Org/Repo:TagOne . +# $ docker build -f Dockerfile -t Org/Repo:TagTwo . +# $ docker images --no-trunc +# +# we will see something like this: +# +# REPOSITORY TAG IMAGE ID CREATED SIZE +# Org/Repo TagOne sha256:e3824d794c0ccf10d2f61291f34e0d7e1e02e30b3d459465bc57d04dd3b65884 30 seconds ago 2.14GB +# Org/Repo TagTwo sha256:e3824d794c0ccf10d2f61291f34e0d7e1e02e30b3d459465bc57d04dd3b65884 30 seconds ago 2.14GB +# +# Notice that IMAGEID and CREATED are the same, since the first time it is +# actually created while the second time it just uses the cached layers. +# So the tag is "just a label" attached to the underlying image. +# +# Putting all together for our test: +# We run `build_image.sh` (on the same machine to allow docker cache) with each +# args order variation, tagging each variation with a different name (ensured +# by the random part of the string). +# We expect: +# - All the tags to exist (tagging works): len(images_to_test) == len(tags_to_test) +# - All tagged images to be actually one and the same under the hood: len(set(digests)) == 1 + + +PY_VERSION=$1 +TAG_1="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" +TAG_2="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" +TAG_3="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" +TAG_4="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" +TAG_5="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" +TAG_6="org/repo:image-${PY_VERSION}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}" + +# Do builds alternating the flags order (-g, -t, -py) +# (which should build only one underlying image) +./build_image.sh -py "${PY_VERSION}" -t "${TAG_1}" -g +./build_image.sh -py "${PY_VERSION}" -g -t "${TAG_2}" + +./build_image.sh -g -py "${PY_VERSION}" -t "${TAG_3}" +./build_image.sh -g -t "${TAG_4}" -py "${PY_VERSION}" + +./build_image.sh -t "${TAG_5}" -py "${PY_VERSION}" -g +./build_image.sh -t "${TAG_6}" -g -py "${PY_VERSION}" + +# Collect all the images with their tags and ids +IMGS_FILE="test_images.json" +docker images --no-trunc --format "{{json .}}" | jq '{"repo": .Repository, "tag": .Tag, "digest": .ID}' | jq -s > "${IMGS_FILE}" + +python <