Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce docker container production #660

Merged
merged 2 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
.gitignore
.dockerignore
114 changes: 114 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Publish Docker Container Images
on:
push:
branches: [main]

env:
REGISTRY: ghcr.io
REPO_NAME: ${{ github.repository }}

permissions:
contents: read
packages: write

jobs:

# ------------------------------------------------------------------------------------------
# To be decided: Script-Deploy or Dockerfile-Deploy:
# Script:
# + Separates the golang build from the corso build.
# - Haven't figured out multiplatform builds yet.
# - Doesn't cache, always takes 10-15 minutes per build in the matrix.
# Dockerfile:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dockerfile seems reasonable - I do think we need to move the corso binary build out of the Dockerfile though - that is likely the slow part here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely the slow part. Can you explain more about moving the binary build out of the dockerfile? I'm still trying to wrap my head around docker, and I'm not seeing how we'd pull that part out without being left with, functionally, the scripted approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scripted approach: ./build-container.sh which internally calls ./build.sh to build corso. Uses build/Dockerfile

"Dockerfile approach" (very similar): Uses docker/Dockerfile which is a variation of build/Dockerfile that builds corso as part of Docker build. Uses docker/build-push-action

I'm suggesting we always use ./build.sh as a step in the deploy to build the corso binary and then use build/Dockerfile with the docker/build-push-action action to build and push the image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I made another branch to try out this combaintion:

Here's the diff.
The build result.
And the resulting image.

It mostly works. For some reason the tag isn't coming through on the docker pull. But otherwise seems to be handling it fine. Let me know if that's along the lines of what you were thinking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks about right. ./build.sh does currently build within a container and I think for CI we want to invoke go build ... directly (and enable GO caching). That should hopefully speed things up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to invoke go build ... directly, but we're getting some cross-compilation failures with the azure libraries. Error messages can be seen here. Source seems to be this issue with CGO and cross-compilation.

If you could kindly check out the rest of the changes before I fold them into this PR, they're here.

# + Once cached, takes <1m to deploy.
# + Multiplatform.
# + Extended features (such as tagging) can be handled by more github actions.
# - When not cached, can take >2 hours to build (at least initially).
# - Currently includes the complete golang:1.18 image.
# ------------------------------------------------------------------------------------------

Script-Deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: build
strategy:
matrix:
BUILD_ARCH: [amd64, arm64]
BUILD_OS: [linux]
env:
IMAGE_PREFIX: ghcr.io
VERSION_SUFFIX: rolling
ryanfkeepers marked this conversation as resolved.
Show resolved Hide resolved
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Run build script
run: >
./build-container.sh
--arch ${{ matrix.BUILD_ARCH }}
--prefix ${{ env.IMAGE_PREFIX }}
--suffix ${{ env.VERSION_SUFFIX }}

# login step boilerplate from:
# https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio
- name: Log in to registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin

- name: Push image
env:
IMAGE_ID: ${{ env.IMAGE_PREFIX }}/alcionai/corso
VERSION: ${{ matrix.BUILD_OS }}-${{ matrix.BUILD_ARCH }}-${{ env.VERSION_SUFFIX }}
run: |
docker images -a
docker push ${{ env.IMAGE_ID }}:${{ env.VERSION }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what sort of naming scheme are we going for? Looking at some of the containers on dockerhub it seems many of them use the format <image name>:<program version>-<os> (e.x. redis:6.2.7-bullseye or redis:6.2.7-alpine) with the arch information stored in a manifest that dockerhub takes care of. I'm not sure how ghcr handles different architectures

Copy link
Contributor Author

@ryanfkeepers ryanfkeepers Aug 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Punt: we're not pinning down the naming structure as part of this PR, instead just trying to get anything out. Likely @gmatev and others will have future concerns about that design. I'm sure it'll be some variation/combination of version|schedule|platform.

On that note, we still need to set the foundation for Corso versioning semantics and release control patterns. I expect that much of the version tagging will cascade as a result of that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Dockerfile-Deploy:
runs-on: ubuntu-latest
env:
TARGETOS: linux
TARGETARCH: arm64
steps:
- name: Checkout repository
uses: actions/checkout@v3

# apparently everyone uses this step
- name: Set up QEMU
uses: docker/setup-qemu-action@v2

# setup Docker buld action
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2

# In case we want to switch to dockerhub
# - name: Login to DockerHub
# uses: docker/login-action@v2
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}

# retrieve credentials for ghcr.io
- name: Login to Github Packages
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# build the image
- name: Build image and push to Docker Hub and GitHub Container Registry
uses: docker/build-push-action@v3
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/alcionai/corso:rolling
# use the github cache
cache-from: type=gha
cache-to: type=gha,mode=max

# check the image digest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ TODO - Link to the appropriate page in the published docs.
./build/build-container.sh
```

## Contribution Guidelines
# Containers

Corso images are hosted on [ghrc.io](https://github.com/alcionai/corso/pkgs/container/corso).

Rolling release
```sh
docker pull ghcr.io/alcionai/corso:{SHA} --platform linux/arm64
```

# Contribution Guidelines

TODO

Expand Down
15 changes: 15 additions & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1

# This dockerfile is configured to be run by the /corso/build/build-container.sh
# script. Using docker to build this file directly will fail.

FROM gcr.io/distroless/base-debian10
# FROM gcr.io/distroless/base:debug

WORKDIR /

COPY ./bin/corso ./

USER nonroot:nonroot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure we can have everything mapped from /app/corso since this is what we are recommending in the docs (https://github.com/alcionai/corso/pull/658/files#diff-289cef99004de6fb5ad1a419ad9b4c2d491be1c7558eebfacc4ca484fd0eb58a). The goal is to simplify mapping so that users only map a single folder and we set everything in there nicely organized.

For example:

  • /app/corso/config/corso.toml
  • /app/corso/config/logs - we don't have logs yet, but soon
  • Anything else that makes sense - maybe various Kopia directories if that is reasonable

I think the simplest way is to set environment variables that specify locations (and Corso picks up) and make sure the right folders exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say map, in this case do you mean just to make sure we COPY /app/corso /app/corso in the dockerfile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a request outside of the docker build; making sure that our system plays nicely (via defaults, etc), with the /app/corso location?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanfkeepers sorry this notification did not pop-up.

I mean the latter. Our docs will tell you to map a local folder to /app/corso inside the container. The environment in the container should then be setup that the Corso interactions go to /app/corso

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made an issue to track it. Feel free to add/change the details there.


ENTRYPOINT ["/corso"]
46 changes: 36 additions & 10 deletions build/build-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ usage() {
echo "-----"
echo "Flags"
echo " -h|--help Help"
echo " |--arm Set the architecture to arm64 (default: amd64)"
echo " -a|--arch Set the architecture to the specified value (default: amd64)"
echo " -p|--prefix Prefixes the image name."
echo " -s|--suffix Suffixes the version."
echo " "
echo "-----"
echo "Example Usage:"
echo " ./build/build-container.sh"
echo " ./build/build-container.sh --arm"
echo " ./build/build-container.sh --arch arm64"
echo " ./build/build-container.sh --arch arm64 --prefix ghcr.io --suffix nightly"
echo " "
exit 0
}
Expand All @@ -25,6 +28,8 @@ PROJECT_ROOT=$(dirname ${SCRIPT_ROOT})

OS=linux
ARCH=amd64
IMAGE_NAME_PREFIX=
IMAGE_TAG_SUFFIX=

while [ "$#" -gt 0 ]
do
Expand All @@ -33,31 +38,52 @@ do
usage
exit 0
;;
--arm)
ARCH=arm64
-a|--arch)
ARCH=$2
shift
;;
-p|--prefix)
IMAGE_NAME_PREFIX=$2
shift
;;
-s|--suffix)
IMAGE_TAG_SUFFIX=$2
shift
;;
-*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
echo "Invalid flag '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
echo "Invalid arg '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
esac
shift
done

IMAGE_TAG=${OS}-${ARCH}-$(git describe --tags --always --dirty)
IMAGE_TAG=${OS}-${ARCH}
if [ ! -z "${IMAGE_TAG_SUFFIX}" ]; then
IMAGE_TAG=${IMAGE_TAG}-${IMAGE_TAG_SUFFIX}
fi

IMAGE_NAME=alcionai/corso:${IMAGE_TAG}
if [ ! -z "${IMAGE_NAME_PREFIX}" ]; then
IMAGE_NAME=${IMAGE_NAME_PREFIX}/${IMAGE_NAME}
fi

${SCRIPT_ROOT}/build.sh --arch ${ARCH}

echo "building container"
echo "-----"
echo "building corso container ${IMAGE_NAME}"
echo "-----"

set -x
docker buildx build --tag ${IMAGE_NAME} \
--platform ${OS}/${ARCH} \
--file ${PROJECT_ROOT}/docker/Dockerfile \
--file ${PROJECT_ROOT}/build/Dockerfile \
${PROJECT_ROOT}
set +x
echo "container built successfully ${IMAGE_NAME}"

echo "-----"
echo "container built successfully"
54 changes: 33 additions & 21 deletions build/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ set -e

SCRIPT_ROOT=$(dirname $(readlink -f $0))
PROJECT_ROOT=$(dirname ${SCRIPT_ROOT})
SRC_DIR=${PROJECT_ROOT}
CORSO_BUILD_ARGS=''

CORSO_BUILD_CONTAINER_DIR=/go/src/github.com/alcionai/corso
CORSO_BUILD_CONTAINER_SRC_DIR=${CORSO_BUILD_CONTAINER_DIR}/src
CORSO_BUILD_CONTAINER=/go/src/github.com/alcionai/corso
CORSO_BUILD_CONTAINER_SRC=${CORSO_BUILD_CONTAINER}/src
CORSO_BUILD_PKG_MOD=/go/pkg/mod
CORSO_BUILD_TMP=/tmp/.corsobuild
CORSO_BUILD_TMP_CACHE=${CORSO_BUILD_TMP}/cache
CORSO_BUILD_TMP_MOD=${CORSO_BUILD_TMP}/mod
CORSO_CACHE=${CORSO_BUILD_TMP_CACHE}
CORSO_MOD_CACHE=${CORSO_BUILD_PKG_MOD}/cache

CORSO_BUILD_ARGS=''

GOVER=1.18
GOOS=linux
GOARCH=amd64

Expand All @@ -25,26 +32,31 @@ do
done

# temporary directory for caching go build
mkdir -p /tmp/.corsobuild/cache
mkdir -p ${CORSO_BUILD_TMP_CACHE}
# temporary directory for caching go modules (needed for fast cross-platform build)
mkdir -p /tmp/.corsobuild/mod
mkdir -p ${CORSO_BUILD_TMP_MOD}

echo "building corso"
set -x
docker run --rm --mount type=bind,src=${SRC_DIR},dst=${CORSO_BUILD_CONTAINER_DIR} \
--mount type=bind,src=/tmp/.corsobuild/cache,dst=/tmp/.corsobuild/cache \
--mount type=bind,src=/tmp/.corsobuild/mod,dst=/go/pkg/mod \
--workdir ${CORSO_BUILD_CONTAINER_SRC_DIR} \
--env GOCACHE=/tmp/.corsobuild/cache \
--env GOOS=${GOOS} \
--env GOARCH=${GOARCH} \
--env GOCACHE=/tmp/.corsobuild/cache \
--entrypoint /usr/local/go/bin/go \
golang:1.18 \
build ${CORSO_BUILD_ARGS}
echo "-----"
echo "building corso binary for ${GOOS}-${GOARCH}"
echo "-----"

mkdir -p ${PROJECT_ROOT}/bin
set -x
docker run --rm \
--mount type=bind,src=${PROJECT_ROOT},dst=${CORSO_BUILD_CONTAINER} \
--mount type=bind,src=${CORSO_BUILD_TMP_CACHE},dst=${CORSO_BUILD_TMP_CACHE} \
--mount type=bind,src=${CORSO_BUILD_TMP_MOD},dst=${CORSO_BUILD_PKG_MOD} \
--workdir ${CORSO_BUILD_CONTAINER_SRC} \
--env GOMODCACHE=${CORSO_MOD_CACHE} \
--env GOCACHE=${CORSO_CACHE} \
--env GOOS=${GOOS} \
--env GOARCH=${GOARCH} \
--entrypoint /usr/local/go/bin/go \
golang:${GOVER} \
build ${CORSO_BUILD_ARGS}
set +x

echo "creating binary image in bin/corso"
mkdir -p ${PROJECT_ROOT}/bin
mv ${PROJECT_ROOT}/src/corso ${PROJECT_ROOT}/bin/corso

echo "-----"
echo "created binary image in ${PROJECT_ROOT}/bin/corso"
26 changes: 22 additions & 4 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
# syntax=docker/dockerfile:1

# This dockerfile is able to make a quick, local image of corso.
# It is not used for deployments.

## Build
FROM golang:1.18 AS base

WORKDIR /src

COPY ./src/go.mod .
COPY ./src/go.sum .
RUN go mod download

COPY ./src .

FROM base AS build
ARG TARGETOS
ARG TARGETARCH
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /corso .

## Deploy
FROM gcr.io/distroless/base-debian10

WORKDIR /
COPY --from=build /corso /

COPY ./bin/corso ./

USER nonroot:nonroot

ENTRYPOINT ["/corso"]
ENTRYPOINT ["/corso"]
12 changes: 12 additions & 0 deletions docker/docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// docker-bake.hcl
target "docker-metadata-action" {}

target "build" {
inherits = ["docker-metadata-action"]
context = "./"
dockerfile = "Dockerfile"
platforms = [
"linux/amd64",
"linux/arm64",
]
}