diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml new file mode 100644 index 0000000..f061eb2 --- /dev/null +++ b/.github/actions/build/action.yaml @@ -0,0 +1,41 @@ +name: "Build & publish images" +description: "Builds images and pushes them to ghcr.io" + +inputs: + context: + description: “Context directory for the build” + required: true + image_name: + description: "Full name (incl. tag)" + required: true + +runs: + using: "composite" + steps: + - name: "Checkout" + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: 'arm64,arm' + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: ${inputs.context} + file: ${inputs.context}/Dockerfile + no-cache: true + platforms: linux/amd64,linux/arm,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: | + ${{ inputs.image_name }} + + diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 8f17ee5..7d67271 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -4,33 +4,20 @@ on: branches: - main jobs: - publish-test-base-image: + build-and-publish-all-images: runs-on: ubuntu-latest - env: - IMAGE_NAME: ghcr.io/crac/test-base steps: - - uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - uses: ./.github/actions/build with: - platforms: 'arm64,arm' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + context: test-base + image_name: ghcr.io/crac/test-base + - uses: ./.github/actions/build with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v4 + context: jdk + image_name: ghcr.io/crac/jdk + - uses: ./.github/actions/build with: - context: test-base - file: test-base/Dockerfile - no-cache: true - platforms: linux/amd64,linux/arm,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: | - ${{ env.IMAGE_NAME }} + context: example-jetty + image_name: ghcr.io/crac/example-jetty + diff --git a/example-jetty/Dockerfile b/example-jetty/Dockerfile new file mode 100644 index 0000000..006f7e5 --- /dev/null +++ b/example-jetty/Dockerfile @@ -0,0 +1,13 @@ +ARG BASEIMG=ghcr.io/crac/jdk + +FROM $BASEIMG AS builder +RUN apt-get update && apt-get install -y git maven +RUN cd /tmp && git clone https://github.com/CRaC/example-jetty +RUN cd /tmp/example-jetty && mvn package + +FROM $BASEIMG +RUN mkdir /opt/example-jetty/ +COPY --from=builder /tmp/example-jetty/target/dependency /opt/example-jetty/dependency +COPY --from=builder /tmp/example-jetty/target/example-jetty*.jar /opt/example-jetty/example-jetty.jar +CMD [ "/usr/bin/java", "-XX:CRaCCheckpointTo=/cr", "-jar", "/opt/example-jetty/example-jetty.jar" ] + diff --git a/jdk/Dockerfile b/jdk/Dockerfile new file mode 100644 index 0000000..a60b2de --- /dev/null +++ b/jdk/Dockerfile @@ -0,0 +1,31 @@ +ARG TAG=17-crac+5 +ARG JDK=openjdk-${TAG}_linux-x64 + +FROM ubuntu:latest AS builder +# We are not using Docker ADD command as this does not cache the file +RUN apt-get update && apt-get install -y wget +ARG TAG +ARG JDK +ARG CHECKSUM=bf4405578cd445e5c6501c76cf72aac555fc76dec35a41a5219e4511c099e1da +RUN cd /opt && wget -nv "https://github.com/CRaC/openjdk-builds/releases/download/$TAG/$JDK.tar.gz" +RUN echo "$CHECKSUM /opt/$JDK.tar.gz" > /opt/checksum +RUN sha256sum --check /opt/checksum +RUN cd /opt && tar xzf $JDK.tar.gz + +FROM ubuntu:latest +ARG JDK +COPY --from=builder /opt/$JDK /opt/$JDK +# Symlink on well-known place is useful for exec form of CMD +RUN ln -s /opt/$JDK/bin/java /usr/bin/java +RUN mkdir /cr && chmod a+rwx /cr +ADD entrypoint.sh /opt/entrypoint.sh +ADD checkpoint.sh /opt/checkpoint.sh +ADD restore.sh /opt/restore.sh +ENTRYPOINT [ "/opt/entrypoint.sh" ] +CMD [ "bash" ] +# Unprivileged restore does not work ATM so we'll use root user +#RUN useradd -m -u 1000 user +#USER user +ENV JAVA_HOME /opt/$JDK +ENV PATH $JAVA_HOME/bin:/opt:$PATH + diff --git a/jdk/checkpoint.sh b/jdk/checkpoint.sh new file mode 100755 index 0000000..d945699 --- /dev/null +++ b/jdk/checkpoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -o pipefail + +if ! echo 1000 | tee /proc/sys/kernel/ns_last_pid > /dev/null 2>&1 ; then + echo "This container is unprivileged, cannot perform checkpoint. Use docker run --privileged ..." >&2 + exit 1; +fi + +PIDS=$(jps -v | grep -e '-XX:CRaCCheckpointTo=' | cut -f 1 -d ' ') +if [ -z "$PIDS" ]; then + echo "No processes to checkpoint." >&2 + exit 1; +fi +for PID in $PIDS; do + IMAGEDIR=$(jps -v | sed -n 's/^'$PID'.*-XX:CRaCCheckpointTo=\([^ \t]*\).*/\1/p') + if ! mount | grep -e " on $IMAGEDIR " > /dev/null; then + echo "WARNING: Image directory $IMAGEDIR does not seem to be bound outside container. Have you forgot -v /path/to/cr:$IMAGEDIR ?" >&2 + fi + jcmd $PID JDK.checkpoint +done diff --git a/jdk/entrypoint.sh b/jdk/entrypoint.sh new file mode 100755 index 0000000..c73a953 --- /dev/null +++ b/jdk/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -o pipefail + +if !(echo "$@" | grep -e '-XX:CRaCRestoreFrom\|restore.sh' > /dev/null) || [ -n "$CHECKPOINT" ]; then + # This is the run that's going to be checkpointed + + # This makes sure that the new process is started with high enough PID + # - otherwise a restore without --privileged wouldn't succeed + if ! echo ${MIN_PID:-1000} | tee /proc/sys/kernel/ns_last_pid > /dev/null 2>&1 ; then + echo "WARNING: This container is unprivileged, cannot perform checkpoint. Use docker run --privileged ..." >&2 + fi + + if echo "$@" | grep -e "-XX:CRaCCheckpointTo" > /dev/null; then + IMAGEDIR=$(echo "$@" | sed -n 's/.*-XX:CRaCCheckpointTo=\([^ \t]*\).*/\1/p') + if ! mount | grep -e " on $IMAGEDIR " > /dev/null; then + echo "WARNING: Image directory $IMAGEDIR does not seem to be bound outside container. Have you forgot -v /path/to/cr:$IMAGEDIR ?" >&2 + fi + + if [ "$1" = "/bin/sh" -a "$2" = "-c" ]; then + echo "It appears that CMD has a shell-form rather than exec-form. This could result in this container exiting before the image directory is fully written." >&2 + echo "For more information about shell-form vs. exec-form please see https://docs.docker.com/engine/reference/builder/#cmd" >&2 + exit 1 + fi + fi +fi + +# We execute right into the process as staying in shell script would cause trouble with propagating signals +exec "$@" diff --git a/jdk/restore.sh b/jdk/restore.sh new file mode 100755 index 0000000..c7fe91e --- /dev/null +++ b/jdk/restore.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +restore() { + echo "Restoring from $1 ..." + IMAGEDIR=$1 + shift + exec /usr/bin/java -XX:CRaCRestoreFrom=$IMAGEDIR "$@" +} + +if [ -n "$IMAGEDIR" ]; then + if [ ! -d "$IMAGEDIR" ]; then + echo "'$IMAGEDIR' is not a directory, cannot restore." >&2 + exit 1 + elif [ ! -f "$IMAGEDIR/cppath" ]; then + echo "Directory $IMAGEDIR does not contain file 'cppath', cannot restore." >&2 + exit 1 + fi + restore $IMAGEDIR "$@" +fi + +CANDIDATES=$(mount | grep /dev/mapper/vgubuntu-root | grep -v -e 'on /etc' | sed -e 's/.* on \(\/[^ ]*\) type.*/\1/') +if [ -z "$CANDIDATES" ]; then + echo "No volumes mounted for C/R found" >&2 + exit 1; +fi +for DIR in $CANDIDATES; do + if [ -f $DIR/cppath ]; then + restore $DIR "$@" + fi +done + +echo "Did not find 'cppath' in any of the available mounts; cannot restore." >&2 +exit 1