WHAT AM I DOING WITH CLOUD RUN JOB
- I am trying to use cloud run job as self hosted runners for github
- I wanted my runner to build and push docker images so I used DinD(Docker in Docker) Pattern as I cannot directly volume mount
/var/run/docker.sock
in cloud run job (which is Docker out of Docker) - so I build my own docker dind image for runner
build-self-hosted-runner: github
Dockerfile and other files
FROM ubuntu:22.04
# RUNNER ENVIRONMENT VARIABLES
ARG RUNNER_VERSION="2.316.0"
ENV GITHUB_PERSONAL_TOKEN ""
ENV GITHUB_OWNER ""
ENV GITHUB_REPOSITORY ""
ENV RUNNER_NAME ""
# END OF RUNNER ENVIRONMENT VARIABLES
RUN apt update
&& apt install -y ca-certificates
wget curl iptables supervisor
&& rm -rf /var/lib/apt/list/*
&& update-alternatives --set iptables /usr/sbin/iptables-legacy
ENV DOCKER_CHANNEL=stable
DOCKER_VERSION=26.0.1
DOCKER_COMPOSE_VERSION=v2.26.1
BUILDX_VERSION=v0.13.1
DEBUG=false
# Docker and buildx installation
RUN set -eux;
arch="$(uname -m)";
case "$arch" in
# amd64
x86_64) dockerArch='x86_64' ; buildx_arch='linux-amd64' ;;
# arm32v6
armhf) dockerArch='armel' ; buildx_arch='linux-arm-v6' ;;
# arm32v7
armv7) dockerArch='armhf' ; buildx_arch='linux-arm-v7' ;;
# arm64v8
aarch64) dockerArch='aarch64' ; buildx_arch='linux-arm64' ;;
*) echo >&2 "error: unsupported architecture ($arch)"; exit 1 ;;
esac;
if ! wget -O docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${dockerArch}/docker-${DOCKER_VERSION}.tgz"; then
echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${dockerArch}'";
exit 1;
fi;
tar --extract
--file docker.tgz
--strip-components 1
--directory /usr/local/bin/
;
rm docker.tgz;
if ! wget -O docker-buildx "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.${buildx_arch}"; then
echo >&2 "error: failed to download 'buildx-${BUILDX_VERSION}.${buildx_arch}'";
exit 1;
fi;
mkdir -p /usr/local/lib/docker/cli-plugins;
chmod +x docker-buildx;
mv docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx;
dockerd --version;
docker --version;
docker buildx version
COPY modprobe start-docker.sh runner.sh entrypoint.sh /usr/local/bin/
COPY supervisor/ /etc/supervisor/conf.d/
COPY logger.sh /opt/bash-utils/logger.sh
RUN chmod +x /usr/local/bin/start-docker.sh
/usr/local/bin/entrypoint.sh
/usr/local/bin/runner.sh
/usr/local/bin/modprobe
VOLUME /var/lib/docker
# Docker compose installation
RUN curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
&& chmod +x /usr/local/bin/docker-compose && docker-compose version
# Create a symlink to the docker binary in /usr/local/lib/docker/cli-plugins
# for users which uses 'docker compose' instead of 'docker-compose'
RUN ln -s /usr/local/bin/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose
# Install the GitHub Actions Runner
RUN apt-get update && apt-get install -y sudo jq
WORKDIR /actions-runner
RUN curl -Ls https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz | tar xz
&& bash ./bin/installdependencies.sh
COPY entrypoint.sh /actions-runner/entrypoint.sh
RUN chmod +x /actions-runner/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# CMD ["bash"]
entrypoint.sh:
#!/bin/bash
# Start docker
start-docker.sh
bash -c runner.sh
start-docker.sh
#!/bin/bash
source /opt/bash-utils/logger.sh
function wait_for_process () {
local max_time_wait=30
local process_name="$1"
local waited_sec=0
while ! pgrep "$process_name" >/dev/null && ((waited_sec < max_time_wait)); do
INFO "Process $process_name is not running yet. Retrying in 1 seconds"
INFO "Waited $waited_sec seconds of $max_time_wait seconds"
sleep 1
((waited_sec=waited_sec+1))
if ((waited_sec >= max_time_wait)); then
return 1
fi
done
return 0
}
INFO "Starting supervisor"
/usr/bin/supervisord -n >> /dev/null 2>&1 &
INFO "Waiting for docker to be running"
wait_for_process dockerd
if [ $? -ne 0 ]; then
ERROR "dockerd is not running after max time"
exit 1
else
INFO "dockerd is running"
fi
runner.sh
#!/bin/bash
echo "Starting runner"
cd /actions-runner || exit 1
registration_url="https://api.github.com/orgs/${GITHUB_OWNER}/actions/runners/registration-token"
echo "Requesting registration URL at '${registration_url}'"
echo "Starting runner"
payload=$(curl -sX POST -H "Authorization: token ${GITHUB_PERSONAL_TOKEN}" ${registration_url})
export RUNNER_TOKEN=$(echo $payload | jq .token --raw-output)
RUNNER_CPU="2"
RUNNER_MEMORY="4GB"
RUNNER_NAME_ARGUMENT_PASSED=$RUNNER_NAME
RUNNER_NAME="quicksilver-${RUNNER_CPU}cpu-${RUNNER_MEMORY}"
if [ -n "$RUNNER_NAME_ARGUMENT_PASSED" ]; then
RUNNER_NAME=$RUNNER_NAME_ARGUMENT_PASSED
fi
echo "RUNNER_NAME: ${RUNNER_NAME}"
random_string() {
cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 10 | head -n 1
}
RUNNER_ALLOW_RUNASROOT=1
export RUNNER_ALLOW_RUNASROOT=1
./config.sh
--name "${RUNNER_NAME}-$(random_string)"
--token ${RUNNER_TOKEN}
--labels ${RUNNER_NAME}
--url https://github.com/${GITHUB_OWNER}
--work "/work"
--unattended
--replace
--ephemeral
remove() {
./config.sh remove --token "${RUNNER_TOKEN}"
}
trap 'remove; exit 130' INT
trap 'remove; exit 143' TERM
./run.sh "$*" &
wait $!
remove
echo "Runner stopped."
inside supervisor/ folder there is single file dockerd.conf
dockerd.conf:
[program:dockerd]
command=/usr/local/bin/dockerd
autostart=true
autorestart=true
stderr_logfile=/var/log/dockerd.err.log
stdout_logfile=/var/log/dockerd.out.log
logger.sh (can ignore: just logging functions)
#!/bin/sh
# Logger from this post http://www.cubicrace.com/2016/03/log-tracing-mechnism-for-shell-scripts.html
function INFO(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [INFO] [${0}] $msg"
}
function DEBUG(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [DEBUG] [${0}] $msg"
}
function ERROR(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [ERROR] $msg"
}
How I tested in local
docker run --privileged -e GITHUB_PERSONAL_TOKEN="<github-token>" -e GITHUB_OWNER="<org-name>" -e RUNNER_NAME="<runner-name-we-use-in-workflow>" docker-image-name
docker exec -it contianer-id bash
root@e321f2254280:/actions-runner# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
–privileged in above docker run command is important else dockerd will not run
What I tried to test and Error
-
I have tried to docker build a frontend(React) from build folder(
npm run build
) -
Dockerfile for frontend:
FROM nginx:alpine
COPY build /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
- The above dockerfile is woking in cloud run
BUT (The Problem)
1.If I change the frontend Dockerfile to:
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY build/ /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx","-g","daemon off;"]
- It throws error in cloud run
#6 [2/4] COPY nginx.conf /etc/nginx/conf.d/default.conf
#6 DONE 0.1s
time="2024-05-04T12:59:49.389187868Z" level=warning msg="Failed to delete conntrack state for 172.17.0.2: invalid argument"
#7 [3/4] RUN rm -rf /usr/share/nginx/html/*
#7 0.289 runc run failed: unable to start container process: unable to apply caps: operation not permitted
time="2024-05-04T12:59:49.551708303Z" level=warning msg="Failed to delete conntrack state for 172.17.0.2: invalid argument"
#7 ERROR: process "/bin/sh -c rm -rf /usr/share/nginx/html/*" did not complete successfully: exit code: 1
------
> [3/4] RUN rm -rf /usr/share/nginx/html/*:
0.289 runc run failed: unable to start container process: unable to apply caps: operation not permitted
------
Dockerfile:4
--------------------
2 | COPY nginx.conf /etc/nginx/conf.d/default.conf
3 | USER root
4 | >>> RUN rm -rf /usr/share/nginx/html/*
5 | COPY build/ /usr/share/nginx/html
6 | EXPOSE 80
--------------------
ERROR: failed to solve: process "/bin/sh -c rm -rf /usr/share/nginx/html/*" did not complete successfully: exit code: 1
21 /usr/local/bin/dockerd
github.com/moby/buildkit/executor/runcexecutor.exitError
/go/src/github.com/docker/docker/vendor/github.com/moby/buildkit/executor/runcexecutor/executor.go:374
- I understand I can just remove the
RUN rm ...
will solve this problem but in future there is a high probability to use rm in Dockerfiles… and not only rm there are many restrictions that we cannot do in cloud run jobs.
Replicate Problem
test-self-runner: github
build-self-hosted-runner: github
Is this the right approach? am I missing something?